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

> Learn how no-mistakes integrates with GitLab using glab v1.5x API. This guide details its Host struct for authentication, merge requests, and CI pipeline monitoring.

- Repository: [Kun Chen/no-mistakes](https://github.com/kunchenguid/no-mistakes)
- Tags: how-to-guide
- Published: 2026-07-25

---

**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`](https://github.com/kunchenguid/no-mistakes/blob/main/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:

```go
// 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:

```go
// 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:

```go
// 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:

```bash
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:

```bash
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:

```go
// 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:

```go
// 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`](https://github.com/kunchenguid/no-mistakes/blob/main/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:

```go
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.