GitLab Backend Specific Flags and API Quirks in no-mistakes: glab CLI Compatibility

The no-mistakes tool handles multiple glab CLI versions and GitLab API edge cases through defensive flag detection, hostname-scoped authentication, and fallback pipelines in internal/scm/gitlab/gitlab.go.

The no-mistakes repository implements a robust GitLab backend that wraps the glab CLI to interact with GitLab instances. Because glab evolves rapidly and GitLab's REST API has specific pagination and encoding requirements, the source code contains several specialized workarounds. These GitLab backend specific flags and API quirks ensure reliable operation across glab v1.4, v1.5x, and newer releases, regardless of whether repositories use legacy configurations or modern project paths.

Host-Scoped Authentication and Context Isolation

To prevent stale credentials on unrelated hosts from poisoning availability checks, the implementation uses host-scoped authentication. When a GitLab host is explicitly configured, the code appends the --hostname flag to glab auth status commands.

In internal/scm/gitlab/gitlab.go (lines 31-35), the authArgs builder constructs the command:

// Host-scoped auth prevents cross-host credential leaks
args := []string{"auth", "status"}
if h.host != "" {
    args = append(args, "--hostname", h.host)
}

This ensures that no-mistakes validates credentials against the specific GitLab instance defined in the configuration, not the default host stored in glab's global config.

Handling glab CLI Version Compatibility

The GitLab backend maintains compatibility across breaking changes in the glab CLI by detecting feature support at runtime rather than assuming version-specific behavior.

The Deprecated --state opened Flag

Older glab versions required the --state opened flag to filter for open merge requests, while v1.5x and later removed this flag in favor of default open-only behavior. The FindPR implementation (lines 68-71) appends only --output json, relying on glab's modern defaults while avoiding errors on newer versions.

Detecting Unsupported --mr Flags

Not every glab release supports the glab ci status --mr flag. The code proactively detects this limitation through stderr analysis before executing fallback logic.

The isUnsupportedMRFlagError function (lines 94-112) scans error output for markers like "unknown flag" or "unsupported option":

func isUnsupportedMRFlagError(out []byte) bool {
    s := strings.ToLower(string(out))
    return strings.Contains(s, "unknown flag") || 
           strings.Contains(s, "unsupported option") ||
           strings.Contains(s, "invalid flag")
}

When detected, GetChecks falls back to a pipeline-job lookup via the MR view instead of the direct --mr flag approach.

REST API Pagination and Path Encoding

GitLab's REST API paginates job lists with a default of 20 items per page. The implementation forces full collection through specific flag combinations and handles URL encoding quirks for project paths.

Forcing Pagination for Pipeline Jobs

The pipelineJobsArgs function (lines 95-107) builds commands that use glab api --paginate to traverse all result pages, then merges the JSON arrays into a single response. This prevents silent data truncation when repositories have more than 20 pipeline jobs.

Segment-by-Segment URL Encoding

The group/project path must be URL-escaped segment-by-segment (e.g., group%2Fproject) before insertion into REST endpoints. The same pipelineJobsArgs routine handles this by splitting on /, escaping each segment individually, and joining with %2F:

func (h *Host) pipelineJobsArgs(pipelineID int) []string {
    if h.projectPath != "" {
        segments := strings.Split(h.projectPath, "/")
        for i, seg := range segments {
            segments[i] = url.PathEscape(seg)
        }
        enc := strings.Join(segments, "%2F")
        return []string{
            "api", "--paginate",
            fmt.Sprintf("projects/%s/pipelines/%d/jobs", enc, pipelineID),
        }
    }
    // Legacy fallback path...
    return []string{
        "ci", "get", "--pipeline-id", fmt.Sprintf("%d", pipelineID),
        "--output", "json", "--with-job-details",
    }
}

Legacy Fallbacks and Defensive Parsing

When modern REST paths are unavailable, the code maintains compatibility through alternative command structures and robust output parsing.

Fallback to glab ci get

When no projectPath is configured (legacy mode), jobs are fetched with glab ci get --pipeline-id …. This approach requires a current branch and cannot run from a detached-HEAD worktree. The pipelineJobsArgs function (lines 91-94) retains this path for backward compatibility while preferring the REST API approach when project paths are available.

Trimming Non-JSON Banner Output

Glab occasionally prepends informational banners before JSON payloads, which breaks naive parsers. The bytesTrimToJSON function (lines 92-100) strips any leading non-JSON characters to ensure valid parsing:

func bytesTrimToJSON(b []byte) []byte {
    // Skip banner lines until we hit JSON array/object
    for i := 0; i < len(b); i++ {
        if b[i] == '[' || b[i] == '{' {
            return b[i:]
        }
    }
    return b
}

Summary

  • Host-scoped authentication prevents credential cross-contamination by appending --hostname to auth commands in internal/scm/gitlab/gitlab.go.
  • Version compatibility is maintained through runtime detection of deprecated flags like --state opened and unsupported flags like --mr.
  • Defensive error handling via isUnsupportedMRFlagError enables graceful fallbacks when glab versions lack specific features.
  • Forced pagination using --paginate ensures complete job lists when querying GitLab's REST API for pipeline status.
  • Segment-by-segment URL encoding correctly handles project paths containing slashes by encoding each part separately before joining with %2F.
  • Legacy support retains glab ci get fallback for configurations lacking explicit project paths, despite detached-HEAD limitations.

Frequently Asked Questions

What triggers the fallback from --mr to legacy pipeline lookups?

The fallback triggers when isUnsupportedMRFlagError detects "unknown flag" or "unsupported option" messages in glab's stderr output. This occurs on glab versions older than v1.5 that do not recognize the --mr flag in glab ci status commands.

How does no-mistakes handle GitLab API pagination?

When a projectPath is configured, the pipelineJobsArgs function builds a command using glab api --paginate to automatically traverse all result pages. The implementation then merges the paginated JSON arrays into a single response to prevent the default 20-item limit from truncating job lists.

Why is URL encoding necessary for project paths?

GitLab's REST API requires the group/project path to appear URL-encoded in the endpoint URL. The code splits the path on /, escapes each segment individually using url.PathEscape, and rejoins them with %2F to ensure proper handling of special characters while maintaining the API's expected format.

Which glab versions are compatible with no-mistakes?

The code supports glab v1.4 through v1.5x and later by using defensive programming techniques. It detects unsupported flags at runtime, removes deprecated flags like --state opened for newer versions, and provides legacy fallbacks for older CLI versions that cannot use modern REST API endpoints.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →