# GitLab Backend Gotchas with glab Version Drift: How no-mistakes Handles CLI Changes

> Avoid GitLab CLI version drift issues with no mistakes. Learn how to conditionally add flags fallback to REST APIs and sanitize JSON for glab v1.4 to v1.5+ compatibility.

- Repository: [Kun Chen/no-mistakes](https://github.com/kunchenguid/no-mistakes)
- Tags: deep-dive
- Published: 2026-07-17

---

**The no-mistakes GitLab backend defends against glab CLI version drift by conditionally adding flags, falling back to REST APIs, and sanitizing JSON output to ensure compatibility across glab v1.4 through v1.5+ releases.**

The `kunchenguid/no-mistakes` repository provides a GitLab SCM integration that relies on the `glab` command-line tool. Because `glab` evolves rapidly, command signatures and behaviors shift between versions, creating **GitLab backend gotchas with glab version drift** that could break automation. The implementation uses defensive patterns scattered throughout [`internal/scm/gitlab/gitlab.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/scm/gitlab/gitlab.go) to shield the daemon from these breaking changes.

## Authentication Flag Scoping

Older `glab` versions used `glab auth status` without hostname scoping, while newer releases support `--hostname` to limit checks to a specific instance. The `Host.Available` method in [`internal/scm/gitlab/gitlab.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/scm/gitlab/gitlab.go) (lines 21-31) adds `--hostname <host>` when a host is known, falling back to unscoped checks for unknown hosts. This prevents stale credentials on different GitLab instances from poisoning availability detection.

## Removed Flags and Default Behaviors

### The Missing `--state opened` Flag

Pre-v1.5 `glab mr list` required an explicit `--state opened` flag, but v1.5 removed this option in favor of default "open" behavior. The `no-mistakes` implementation builds the command **without** any `--state` flag in [`internal/scm/gitlab/gitlab.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/scm/gitlab/gitlab.go) (lines 63-71), relying on the default. The test `TestFindPRDoesNotPassRemovedStateFlag` asserts that the flag is never sent, preventing errors on newer glab versions.

### The Unstable `--mr` Flag for CI Status

Some `glab` releases dropped the `--mr` flag for `glab ci status`. The `Host.GetChecks` method (lines 80-89) first attempts `glab ci status --mr <id>`. If the output contains an unsupported flag pattern (detected by `isUnsupportedMRFlagError`), it falls back to a REST-API path using `mr view` to retrieve pipeline jobs. This ensures CI checks work regardless of whether the flag exists.

## Output Parsing Resilience

### Banner Line Sanitization

`glab` occasionally prints notice lines before JSON output, which breaks naive parsers. The `bytesTrimToJSON` helper at lines 92-100 skips everything up to the first `{` or `[`, ensuring the JSON payload is correctly extracted even when glab prepends informational banners.

### Paginated JSON Streams

When using `glab api --paginate`, GitLab returns one JSON array per page (defaulting to 20 jobs per page). The `decodeGitlabJobs` function (lines 127-146) reads a stream of concatenated JSON documents, handling both array and object forms while surfacing decode errors from later pages. This prevents silent truncation of large job lists.

## Edge Cases in Project Paths and Job States

### Windows Drive Letter Conflicts

Paths like `C:\foo` contain colons that could be mistaken for SCP-style `host:path` syntax. The `ProjectPath` function (lines 59-71) calls `isWindowsDrivePath` to reject such strings, returning an empty project path and avoiding REST calls that would target non-existent projects.

### Manual Job State Mapping

The `gitlabStatusBucket` mapper (lines 155-166) explicitly treats `manual` jobs as "skipped" rather than failed. This prevents manual jobs from triggering false CI failure verdicts in merge request checks.

### Mergeable State Normalization

GitLab's `detailed_merge_status` and legacy `merge_status` fields vary between API versions. `GetMergeableState` (lines 42-64) normalizes values like `mergeable` and `can_be_merged` to internal `scm.Mergeable*` constants, abstracting version-specific strings.

## Implementation Examples

The following patterns demonstrate how to instantiate and use the drift-resistant GitLab backend:

**Creating a host with scoped authentication:**

```go
import (
    "context"
    "os/exec"

    "github.com/kunchenguid/no-mistakes/internal/scm/gitlab"
)

func myCmdFactory(ctx context.Context, name string, args ...string) *exec.Cmd {
    return exec.CommandContext(ctx, name, args...)
}

host := gitlab.New(
    myCmdFactory,
    func() bool { return true }, // glab binary present
    "gitlab.example.com",       // host scopes auth check
    "group/project",            // enables REST API fallback
)

if err := host.Available(context.Background()); err != nil {
    // handle missing auth or binary
}

```

**Fetching CI checks with automatic fallback:**

```go
pr := &scm.PR{Number: "123"}
checks, err := host.GetChecks(context.Background(), pr)
if err != nil {
    log.Fatalf("CI check retrieval failed: %v", err)
}

```

**Retrieving failed job logs (works even without `--mr` flag):**

```go
logs, err := host.FetchFailedCheckLogs(
    context.Background(),
    &scm.PR{Number: "123"},
    "", "",
    []string{"lint"},
)

```

The `FetchFailedCheckLogs` implementation (lines 48-78) first obtains the pipeline ID via `mr view`, then lists jobs and runs `glab ci trace <id>`, using the same fallback logic as `GetChecks` when flags are unsupported.

## Summary

- **Auth scoping**: Use `--hostname` when available, fall back to unscoped checks to avoid cross-host credential pollution.
- **Removed flags**: Omit `--state opened` entirely; detect unsupported `--mr` flags and fallback to REST API calls.
- **JSON parsing**: Strip banner lines before the first `[` or `{` to handle glab's informational output.
- **Pagination**: Handle concatenated JSON arrays from paginated API responses.
- **Path validation**: Reject Windows drive paths to prevent SCP-style URL misinterpretation.
- **State mapping**: Normalize manual jobs to "skipped" and mergeable states to internal constants.

## Frequently Asked Questions

### How does no-mistakes handle glab versions that don't support the `--mr` flag?

The `Host.GetChecks` method attempts `glab ci status --mr <id>` first. If `isUnsupportedMRFlagError` detects a known "unsupported flag" pattern in the error output, it falls back to a REST-API path using `mr view` to fetch pipeline jobs. This logic resides in [`internal/scm/gitlab/gitlab.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/scm/gitlab/gitlab.go) (lines 80-89).

### Can no-mistakes parse glab output when the CLI prints warning banners?

Yes. The `bytesTrimToJSON` helper at lines 92-100 discards all characters before the first JSON array or object delimiter (`[` or `{`). This ensures the parser receives clean JSON even when glab prepends informational notices or deprecation warnings.

### Why does the GitLab backend reject Windows paths like `C:\project`?

The `ProjectPath` function uses `isWindowsDrivePath` (lines 59-71) to detect drive-letter paths. Since these contain colons that resemble SCP-style `host:path` syntax, rejecting them prevents incorrect REST API calls to non-existent projects like `C` on the GitLab server.

### How does the backend handle paginated job lists from large pipelines?

The `decodeGitlabJobs` function (lines 127-146) treats the input as a stream of concatenated JSON documents. It handles both the multiple array format returned by `glab api --paginate` and single object responses, aggregating all jobs across pages rather than stopping at the first array.