How no-mistakes Integrates with GitLab Using the glab v1.5x API

no-mistakes delegates all GitLab backend operations to the glab CLI rather than using raw HTTP clients, implementing a Host struct in internal/scm/gitlab that executes version-aware glab commands for authentication, merge requests, and CI pipeline monitoring.

The kunchenguid/no-mistakes repository abstracts GitLab interactions through a clean interface that leverages glab v1.5x capabilities for robust GitOps workflows. This integration strategy eliminates manual token management while supporting advanced features like host-scoped authentication, merge request state tracking, and comprehensive CI check monitoring through structured command-line invocations.

Host Architecture and Construction

The GitLab integration centers on the Host type defined in internal/scm/gitlab/gitlab.go, which implements the generic scm.Host interface used throughout the codebase.

The constructor accepts a command factory and runtime configuration:

// internal/scm/gitlab/gitlab.go:30-45
func New(cmd CmdFactory, cliAvailable func() bool, host, projectPath string) *Host {
    return &Host{
        cmd:          cmd,
        cliAvailable: cliAvailable,
        host:         strings.TrimSpace(host),
        projectPath:  strings.TrimSpace(projectPath),
    }
}

Key dependencies injected:

  • cmd: A CmdFactory that creates exec.Cmd instances to run glab in the caller's working directory
  • cliAvailable: A runtime predicate that verifies the glab binary exists on PATH
  • host: The GitLab instance hostname for authentication scoping (e.g., gitlab.example.com)
  • projectPath: The repository path (e.g., group/project) enabling REST-based job queries independent of the current branch

Authentication Scoping and Availability

The Available method ensures the CLI is installed and authenticated before executing operations. It supports both scoped and unscoped authentication checks:

// internal/scm/gitlab/gitlab.go:21-34
func (h *Host) Available(ctx context.Context) error {
    if h.cliAvailable != nil && !h.cliAvailable() {
        return errors.New("glab CLI is not installed")
    }
    authArgs := []string{"auth", "status"}
    if h.host != "" {
        authArgs = append(authArgs, "--hostname", h.host)
    }
    if err := h.cmd(ctx, "glab", authArgs...).Run(); err != nil {
        return errors.New("glab CLI is not authenticated")
    }
    return nil
}

When a host is specified, the code executes glab auth status --hostname <host> to prevent stale credentials for unrelated GitLab instances from causing false negatives. If the host is empty, the check falls back to the default unscoped authentication status for backward compatibility.

Merge Request Operations

The Host type implements the complete merge request lifecycle through glab's mr subcommand family. All operations return standardized scm.PR structs that decouple the GitLab implementation from the rest of the application.

Finding Existing Merge Requests

The FindPR method queries for merge requests by source branch:

// Implementation around lines 63-73
args := []string{"mr", "list", "--source-branch", branch, "--output", "json"}
if base != "" {
    args = append(args, "--target-branch", base)
}
// Execute and unmarshal first MR payload into *scm.PR

Creating and Updating MRs

CreatePR constructs a glab mr create command with mandatory flags:

glab mr create --source-branch <branch> --target-branch <base> --title <title> --description <body> --yes

The implementation captures stdout and extracts the merge request URL using extractMRURL, optionally parsing the MR number from the response.

UpdatePR resolves the MR identifier and executes:

glab mr update <id> --title <title> --description <body> --yes

Mergeability and State Detection

The GetPRState method calls glab mr view <id> --output json through the internal viewMR helper, then normalizes GitLab's status strings using normalizePRState.

For mergeability checks, GetMergeableState inspects the has_conflicts and detailed_merge_status fields from the mr view JSON output, returning scm.MergeableOK, scm.MergeableConflict, or scm.MergeablePending accordingly.

CI Pipeline Integration

The GetChecks method monitors pipeline status through a dual-strategy approach that maintains compatibility across glab versions.

Primary Strategy: Native CI Status

For glab v1.5x and later, the method uses the dedicated CI status command:

// internal/scm/gitlab/gitlab.go:80-92
cmd := h.cmd(ctx, "glab", "ci", "status", "--mr", pr.Number, "--output", "json")

This command returns a JSON array of job objects that the code decodes and converts to scm.Check structs via jobsToChecks.

Fallback Strategy: REST API

When the installed glab version lacks the --mr flag, the code executes getChecksFallback (lines 118-140):

  1. Retrieve the pipeline ID via glab mr view <id> --output json
  2. Query jobs using paginated API calls: glab api projects/<group%2Fproject>/pipelines/<pipelineID>/jobs

The decodeGitlabJobs helper handles multiple JSON document formats including single arrays, pipeline objects with embedded jobs fields, and concatenated paginated responses.

Retrieving Failed Job Logs

When CI checks fail, FetchFailedCheckLogs provides granular log access without requiring additional authentication:

// Implementation starts at line 48-77
func (h *Host) FetchFailedCheckLogs(ctx context.Context, pr *scm.PR, _, _ string, failedJobNames []string) (string, error) {
    // 1. Retrieve pipeline ID from mr view
    // 2. List jobs via glab api
    // 3. Find matching job ID via findFailedJobID
    // 4. Execute: glab ci trace <jobID>
}

This approach ensures that debug information is available even when the primary CI status command fails or returns incomplete data.

Utility Helpers and Data Normalization

The integration includes several utility functions in internal/scm/gitlab/gitlab.go for robust JSON handling:

  • bytesTrimToJSON: Discards banner lines that glab might emit before JSON payloads
  • normalizePRState: Maps GitLab status values to generic scm enums
  • gitlabStatusBucket: Categorizes job statuses (success, failed, pending) into scm.CheckBucket types
  • ProjectPath: Parses HTTPS, SSH, and scp-style remote URLs into group/project format for API calls

Complete Integration Example

The following example demonstrates the full lifecycle of the no-mistakes GitLab integration:

package main

import (
    "context"
    "fmt"
    "os/exec"

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

func main() {
    // 1️⃣ Build a CmdFactory that runs commands in the current directory.
    cmdFactory := func(ctx context.Context, name string, args ...string) *exec.Cmd {
        return exec.CommandContext(ctx, name, args...)
    }

    // 2️⃣ Create a GitLab host for https://gitlab.example.com/group/project
    host := gitlab.New(
        cmdFactory,
        func() bool { return true }, // assume glab is on PATH
        "gitlab.example.com",        // hostname for auth scoping
        "group/project",             // projectPath enables API job queries
    )

    // 3️⃣ Verify the CLI is present and authenticated.
    if err := host.Available(context.Background()); err != nil {
        panic(err)
    }

    // 4️⃣ Find an existing merge request for the current branch.
    mr, err := host.FindPR(context.Background(), "feature-branch", "main")
    if err != nil {
        panic(err)
    }
    fmt.Printf("Found MR: %s (number %s)\n", mr.URL, mr.Number)

    // 5️⃣ Retrieve CI checks for that MR.
    checks, err := host.GetChecks(context.Background(), mr)
    if err != nil {
        panic(err)
    }
    for _, c := range checks {
        fmt.Printf("Job %s: %s (completed %v)\n", c.Name, c.Bucket, c.CompletedAt)
    }

    // 6️⃣ If any job failed, fetch its log.
    failingNames := []string{}
    for _, c := range checks {
        if c.Bucket == scm.CheckBucketFail {
            failingNames = append(failingNames, c.Name)
        }
    }
    if len(failingNames) > 0 {
        log, _ := host.FetchFailedCheckLogs(context.Background(), mr, "", "", failingNames)
        fmt.Println("Failed job log:\n", log)
    }
}

This pattern instantiates the Host with dependency injection, verifies availability, locates merge requests, monitors CI status, and retrieves failure logs—all mediated through the glab CLI.

Summary

  • no-mistakes implements GitLab integration through the internal/scm/gitlab package, avoiding direct HTTP API management
  • The Host struct encapsulates glab v1.5x command execution with support for host-scoped authentication via glab auth status --hostname
  • Merge request operations use glab mr list, create, update, and view commands with JSON output parsing
  • CI monitoring prefers glab ci status --mr but falls back to glab api calls for older glab versions
  • Failed job logs are retrieved through glab ci trace after resolving job IDs via the pipeline API
  • All interactions are abstracted through the scm.Host interface, enabling testable, version-resilient GitOps workflows

Frequently Asked Questions

How does no-mistakes handle authentication for multiple GitLab instances?

The Host struct accepts an optional host parameter that scopes authentication checks to specific GitLab instances. When provided, the Available method executes glab auth status --hostname <host> to verify credentials for that specific instance only, preventing conflicts with cached tokens for other GitLab URLs.

What happens if the glab CLI version doesn't support the --mr flag for CI checks?

The GetChecks method implements automatic fallback logic in getChecksFallback. It first retrieves the pipeline ID using glab mr view, then queries jobs directly via glab api projects/<path>/pipelines/<id>/jobs. This ensures compatibility with glab versions predating the ci status --mr feature.

Why does no-mistakes use the glab CLI instead of the GitLab REST API directly?

Using glab abstracts authentication management, pagination handling, and JSON formatting while eliminating the need to store and refresh personal access tokens within the application. This approach leverages the user's existing glab configuration and credential helpers, reducing security surface area and maintenance overhead.

How are GitLab job statuses mapped to the generic scm.Check format?

The integration uses gitlabStatusBucket and normalizePRState helper functions to translate GitLab-specific status strings (like detailed_merge_status and job states) into standardized scm.CheckBucket enums (Success, Fail, Pending) and mergeability constants that the rest of the no-mistakes pipeline can process agnostically.

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 →