GitLab Backend Gotchas with glab Version Drift: How no-mistakes Handles CLI Changes
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 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 (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 (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:
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:
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):
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
--hostnamewhen available, fall back to unscoped checks to avoid cross-host credential pollution. - Removed flags: Omit
--state openedentirely; detect unsupported--mrflags 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 (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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →