Terminal-based GitLab CI client — browse pipelines, stream logs, and triage failures from your terminal.
- 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 whileddgl attach --retrypolls, or pressrin 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 —
--jsonoutput on every command, pipe-friendly,--no-cachefor force-refresh - Local caching — finished pipelines and logs cached for one week; repeated queries are instant
Requires Python ≥ 3.12.
# Recommended — uv
uv tool install git+https://github.com/DataDog/ddgl-cli
# Or pip
pip install git+https://github.com/DataDog/ddgl-cliSet 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 |
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 = falsegitlab_url— same asGITLAB_URL; used when the env var is unset.token_file— path to a file containing the token (read and stripped). Checked beforetoken_command.token_command— a command, as a TOML array (no shell involved), that prints a token to stdout.github_fallback— iftrueand 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.
# 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>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 pipelinesddgl 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 jobsddgl 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 promptWith 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.
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 stageddgl 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'ddgl format /path/to/trace.log
cat trace.log | ddgl format -ddgl --no-cache pipelines get
ddgl --no-cache logs --failedBy 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 50ddgl viz # current branch
ddgl viz --ref feature/foo # specific branch
ddgl viz --pipeline 98765 # specific pipeline IDLayout: 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"
| 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 |
| 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)
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 124ddgl 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.
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- 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
uv sync
uv run pytest -v
uv run ruff check --fixSee DEVELOPER.md for the overall architecture and contributor guidelines.