Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

ddgl

CI

Terminal-based GitLab CI client — browse pipelines, stream logs, and triage failures from your terminal.

Features

  • Branch-aware — auto-detects your current git branch and resolves the latest pipeline with no arguments
  • Interactive TUI (ddgl viz) — full pipeline browser with job list, status/stage filters, fuzzy or regex search, and matrix job grouping
  • Blocking wait (ddgl attach) — block until a pipeline finishes, streaming progress; a live status line for humans, an append-only/JSONL event stream for scripts and coding agents
  • Retry support — retry failed jobs with ddgl retry, auto-retry them while ddgl attach --retry polls, or press r in the TUI
  • Job detail view — streaming log with collapsible sections, dependency graph (DAG), and keyboard navigation
  • Smart log formatting — ANSI colors preserved, sections folded, optional timestamps, syntax highlighting
  • Scripting-friendly--json output on every command, pipe-friendly, --no-cache for force-refresh
  • Local caching — finished pipelines and logs cached for one week; repeated queries are instant

Installation

Requires Python ≥ 3.12.

# Recommended — uv
uv tool install ddgl

# Or pip
pip install ddgl

To track main instead of the latest release, install from git:

uv tool install git+https://github.com/DataDog/ddgl-cli

Authentication & Configuration

Set GITLAB_TOKEN to a personal access token with read_api scope. Alternatively, configure token_file or token_command in the config file to resolve a token automatically.

The project is auto-detected from the origin git remote when run inside a repository. You can override any setting with environment variables:

Variable Default Description
GITLAB_TOKEN Personal access token
GITLAB_URL gitlab.com GitLab instance base URL
GITLAB_PROJECT_ID auto-detected Project path, e.g. my-group/my-project
DDGL_CONFIG_FILE see below Path to the TOML config file
DDGL_GITHUB_FALLBACK false Assume the GitHub remote's org/repo path on GitLab too
DDGL_LOG_LEVEL WARNING Log verbosity: DEBUG, INFO, WARNING, ERROR

Config file

For settings you don't want to repeat as environment variables, ddgl reads an optional TOML config file from the platform's standard config directory (e.g. ~/.config/ddgl/config.toml on Linux/macOS), or the path given by DDGL_CONFIG_FILE:

gitlab_url = "https://gitlab.example.com"
token_file = "/run/secrets/gitlab-token"
token_command = ["my-auth-tool", "print-token"]
github_fallback = false
  • gitlab_url — same as GITLAB_URL; used when the env var is unset.
  • token_file — path to a file containing the token (read and stripped). Checked before token_command.
  • token_command — a command, as a TOML array (no shell involved), that prints a token to stdout.
  • github_fallback — if true and no GitLab remote is found, assume the first GitHub remote's org/repo path is also the GitLab project path. Off by default.

Environment variables always take precedence over the config file.

Quick Start

# Latest pipeline for current branch
ddgl pipelines get

# Open interactive TUI
ddgl viz

# List failed jobs for the latest pipeline on the current branch
ddgl jobs list --failed

# Show the logs for a specific job
ddgl logs --job <id>

Common Workflows

Check pipeline status

ddgl pipelines get                  # latest pipeline on current branch
ddgl pipelines get --ref main       # specific branch
ddgl pipelines list -n 10           # last 10 pipelines
ddgl pipelines list --scope running # only running pipelines

Triage failed jobs

ddgl jobs list --failed
ddgl jobs list --failed --stage build
ddgl jobs list --name "lint.*"      # regex filter on job name
ddgl jobs get --failed              # full details for all failed jobs

Retry failed jobs

ddgl retry                          # retry every failed/canceled job in the latest pipeline
ddgl retry --stage build            # only failed/canceled jobs in the "build" stage
ddgl retry --job 12345 --job 12346  # specific jobs by ID
ddgl retry --job 12345 --force      # retry even if that job already succeeded or is still running
ddgl retry -y                       # skip the confirmation prompt

With no filter, ddgl retry calls GitLab's pipeline-level retry endpoint — the same button the web UI has, and GitLab picks the exact set. Any of --job/-f/--stage/--name narrows to matching jobs instead, each retried individually. Only failed or canceled jobs are retried unless --force widens that to whatever the filter matched. Always asks for confirmation first unless -y/--yes is given. Exit codes: 0 retried (or nothing to do), 1 GitLab rejected a retry, 2 usage/config/resolution error.

Read job logs

ddgl logs --failed                  # all failed logs in current pipeline
ddgl logs --job 12345               # single job by ID
ddgl logs --job 12345 --raw         # skip formatting
ddgl logs --job 12345 --timestamps  # include ISO timestamps
ddgl logs --job 12345 --strip       # strip ANSI codes (plain text)
ddgl logs --failed --stage test     # failed logs filtered to one stage

Save or pipe output

ddgl logs --job 12345 --output /tmp/build.log
ddgl logs --job 12345 --output /tmp/logs/      # one file per job (already-existing directory)
ddgl logs --failed --strip --output /tmp/logs/ # plain-text dump of all failed logs
ddgl jobs list --failed --json | jq '.[].name'
ddgl pipelines list --json | jq '.[] | select(.status == "failed") | .id'

Format a saved trace

ddgl format /path/to/trace.log
cat trace.log | ddgl format -

Force-refresh (bypass cache)

ddgl --no-cache pipelines get
ddgl --no-cache logs --failed

Walk back through commit history

By default ddgl searches the last 10 commits for a pipeline. Increase --depth when working on a branch with many commits since the last pipeline run:

ddgl pipelines get --depth 50
ddgl jobs list --failed --depth 50

Interactive TUI (ddgl viz)

ddgl viz                     # current branch
ddgl viz --ref feature/foo   # specific branch
ddgl viz --pipeline 98765    # specific pipeline ID

Layout: pipeline list sidebar (left) · job table (center) · filter bar and footer (bottom).

Search: fuzzy match by default. Toggle regex with the .* button. Use special tokens to combine filters:

status:failed stage:build lint    # failed jobs in "build" stage matching "lint"

Keybindings — main view

Key Action
/ Focus search
Ctrl+K Clear search
s Cycle sort: Stage → A–Z → Start time
Space Expand / collapse matrix job group
r Retry selected job
Ctrl+R Refresh pipeline and jobs
p Switch pipeline (focus sidebar)
o Open job or pipeline in browser
? Show help
q Quit

Keybindings — job detail (Enter)

Key Action
/ Search in log
n / N Next / previous match
t Toggle all log sections (collapse / expand)
Ctrl+↑ / Ctrl+↓ Fast scroll
r Retry this job
Escape / q Close

Tabs: Log · Deps (dependency graph) · History (coming soon) · Tests (coming soon)

Blocking on a Pipeline (ddgl attach)

Blocks until a pipeline reaches a terminal state, streaming progress as it goes. Resolves a pipeline the same way as every other command (--ref / --pipeline / --depth), and by default waits for one to appear if you attach right after pushing.

ddgl attach                       # current branch — waits if no pipeline exists yet
ddgl attach --pipeline 98765      # pin a specific pipeline
ddgl attach --timeout 300         # give up after 5 minutes (exit 124) instead of blocking forever
ddgl attach --follow              # switch to a newer pipeline on the ref if one appears (e.g. a re-push)

Output auto-detects the audience, like every other command's console.is_terminal + --json behavior:

Mode When What you get
Live stdout is a TTY, or --live A single redrawing status line: current stage, job counts, elapsed time
Lines stdout isn't a TTY (default), or --plain Append-only, human-readable lines — one per event, always ending in a [FINAL] line with the outcome
JSONL --json (forced even in a TTY) One JSON object per event, same information as Lines mode

--detail {none,minimal,normal,full} controls how much shows up in Lines and Live modes; --json's JSONL always includes everything, regardless of --detail:

Level Lines mode Live mode
none only the final [FINAL] line bare spinner, no text, until the final state
minimal only summary lines (snapshot/poll/heartbeat) ref + job counts + elapsed time only
normal (default) + pipeline transitions, --follow rebinds, and job transitions that reach a terminal state + current stage + ETA
full everything, including in-progress job transitions and failure messages same as normal, but failed jobs are named instead of counted

At every level above none, a changed poll tick prints its transitions first, then one [POLL] rollup line (job counts, failure count, current stage). This keeps transition lines concise while giving each completed poll a single summary. On a quiet tick, no line is printed unless --heartbeat is set; then it prints [BEAT] with the same rollup. --json always receives these events in full fidelity.

Exit codes follow the GNU timeout convention, so shell scripts and CI-babysitting agents can branch on them directly:

Code Meaning
0 Pipeline succeeded
1 Pipeline failed or was canceled
2 Unexpected/config error
124 --timeout elapsed while the pipeline was still running
ddgl attach && ./deploy.sh                  # only deploy on success
ddgl attach --pipeline 98765 --timeout 300  # bounded wait; loop/re-invoke on exit 124

Auto-retry failed jobs

ddgl attach --retry                                # retry failures as they happen
ddgl attach --retry --retry-attempts 0             # unlimited retries per job name
ddgl attach --retry --retry-total 10               # cap the whole run at 10 retries total
ddgl attach --retry --retry-exclude 'flaky-e2e.*'  # never auto-retry matching job names (repeatable)

Off by default. With --retry, any job already failed when you attach — or that fails while polling — is retried automatically, up to --retry-attempts (default 2) per job name and --retry-total (default 50) across the whole run; either set to 0 for unlimited. Passing --retry-attempts, --retry-total, or --retry-exclude without --retry is a usage error (exit 2) rather than a silent no-op. Each accepted retry prints a [RETRY] line, and the final line reports the total count.

Global Options

These flags are available on all commands:

Flag Description
--ref <ref> Git ref (branch, tag, or SHA); defaults to current branch
--pipeline <id> Pin a specific pipeline by ID
--depth <n> Commits to walk when searching for a pipeline (default: 10)
--no-cache Bypass cache reads (writes still populate the cache)
--json JSON output
--no-pager Disable the pager
-v / -vv Verbose / very verbose output
-y / --yes Skip confirmation prompts

Every command and subcommand accepts --help for the full option list:

ddgl --help
ddgl logs --help
ddgl jobs list --help

Roadmap

  • Job history tab — same job across recent pipelines for flakiness detection
  • Test results — parsed test output in job detail view
  • YAML-based dependency graph — replace N API calls with a single git show + parse

Development

uv sync
uv run pre-commit install --install-hooks   # once per clone
uv run pytest -v

The hooks format and lint on commit, and run the tests your change affects. The first commit after a clone runs the whole suite once (~35s) to build testmon's database; later commits take a second or two.

See DEVELOPER.md for the overall architecture and contributor guidelines.

Release files for ddgl 0.4.0rc1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ddgl 0.4.0rc1
File Size Uploaded
ddgl-0.4.0rc1.tar.gz 92.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for ddgl 0.4.0rc1
File Interpreter ABI Platform
ddgl-0.4.0rc1-py3-none-any.whl Python 3 none any Details

Total release size: 229.6 kB

Release files / ddgl-0.4.0rc1.tar.gz

Download URL ddgl-0.4.0rc1.tar.gz
Size 92.7 kB
Tags Source
SHA-256 checksum
How to use checksums
6dc7507e793e05525899d2ae4f15e8b001c1973ff7ad29846940a37985db528a
BLAKE2b-256 checksum
How to use checksums
4a4ee141b76f4e299748977a51106c81fcdb2ef8104edd232fc11fad0735a2b7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 17, 2026.

Transparency log

Release files / ddgl-0.4.0rc1-py3-none-any.whl

Download URL ddgl-0.4.0rc1-py3-none-any.whl
Size 136.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
aed0178ac273d67461033dbbdc56657758e9a79b4569f752ab4f6c839cdc1103
BLAKE2b-256 checksum
How to use checksums
6d7004ea6409f25e54103a604195448b383d9457009d0aa927e28b0d59099e36
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 17, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.4.0rc1 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page