How it works
The design goal is a dashboard that feels local even when the server is huge and far away. Every trick below exists to serve that: fewer round trips, aggressive caching, and never blocking the UI on the network.
The whole fleet in one call
The pipeline tree, pause states, and latest-run statuses all come from a single request to GoCD's /api/dashboard. There's no N+1 walk over groups or pipelines, so the cost of a refresh doesn't grow with how much you have.
The client requests gzip, which matters more than it sounds. On a real GoCD 23.5.0 instance with thousands of pipelines, the dashboard payload is 5.3 MB uncompressed but only 165 KB compressed, and the load dropped from ~20 seconds to 2–4 seconds. The request timeout is a generous 45 seconds regardless, for slower networks.
The tree starts fully collapsed, because a large org's groups expanded at once would be unusable. Press / to fuzzy-filter by name across every group, collapsed or not.
Personalized GoCD views
GoCD's web dashboard lets each user keep personalized view tabs. lazygocd reads those same server-side filters from GoCD's pipeline_selection endpoint and applies them with /api/dashboard?viewName=..., so v switches to the exact view you use in the browser.
Creating a view is terminal-native too: filter with /, press V, name the view, and lazygocd saves the matching pipelines as a whitelist view on the server. The new view appears in GoCD's web UI as well, and writes use If-Match so a concurrent web edit cannot be silently overwritten.
Instant startup from the disk cache
After every successful load, the dashboard is written to ~/.config/lazygocd/dashboard_cache.json. The next launch reads it and paints your whole fleet immediately, before touching the network, while a fresh load runs in the background. The header makes the state honest:
The LED next to the server URL is the connection at a glance: green connected, yellow loading, red error. When the fresh data lands, the view updates in place.
History cache and hover prefetch
Run history and stage details load lazily, per pipeline, via /api/pipelines/:name/history, only when you open one. Two layers keep that from ever feeling lazy:
- In-session cache. Once you've opened a pipeline's history, reopening it later in the same session is instant, served from memory while a background refresh keeps it current.
- Hover prefetch. Resting the mouse cursor on a pipeline row for ~300ms starts loading its history in the background, so pressing enter often finds it already there.
Live console logs with auto-tail
From the Details pane, enter on any job opens its raw console log in a scrollable full-screen view. While the stage is still running, the log refreshes itself every ~3 seconds and follows the newest output; once the stage finishes, it stops polling on its own.
Tailing is incremental: only new lines are fetched while a job runs, and background dashboard polls revalidate with ETag/304, so steady-state network cost is near zero. History paginates as you scroll, older pages loading automatically.
Scrolling up detaches you from the tail so you can read; G jumps back to the bottom and resumes following. Logs are colorized for scanning: failures and errors in red, warnings and stderr in yellow, successes in green, timestamps and agent chatter dimmed. / searches with inline highlighting and n/N jump between matches.
The job view is tabbed like GoCD's own job page: Console, Artifacts (the job's file tree, enter opens a file in your browser), and Materials (the trigger, repo and branch, and the commits behind the run).
Open the log in your own editor. Press e and the current log opens in $VISUAL, $EDITOR, or whatever you set as editor in config.toml. Search, regex, folding and copying are things your editor already does better, so lazygocd hands the buffer over rather than reimplementing them. Inline editors like vim and nvim take over the terminal and hand it back on exit; VS Code and Zed open beside it.
The deployed-commit check follows upstream pipelines. A deploy run usually has no Git material of its own, only a dependency on the build that produced it, so lazygocd walks that chain to find the commit and labels it via <pipeline> #<counter>. That means the check works where it matters most, on the pipeline that actually deploys.
Act on what you see: R reruns a failed run's failed jobs (or the whole stage), T triggers with environment variables, and desktop notifications fire when a favorited pipeline starts failing. Every git material gets its own stale-deploy check, and GitHub Enterprise works via github_api_base in the config.
Deployed-commit check
The question behind most dashboard visits is "is what's deployed actually the latest code?" When a pipeline's latest run has a direct Git material (not a chained upstream-pipeline material), opening its history compares the deployed commit against the newest commit on that branch and answers inline in the Details panel:
If you have the GitHub CLI signed in, lazygocd uses gh auth token automatically, so private repos work with zero setup. Otherwise public repos still work unauthenticated (rate-limited), and @ lets you paste a personal access token. Press o on any run to open its commit on GitHub, or the pending diff when the latest deploy is behind the branch head. The check only runs for the latest run, never for an older row you happen to have selected.
Jump to the GoCD web UI
Some things still belong in the browser: approving a manual stage, editing pipeline config, reading a value stream map across ten upstream pipelines. O opens whatever is selected, so you land on the page you would have clicked four levels deep to reach. The target narrows as focus moves right:
| Selected | Opens |
|---|---|
| A group header | The GoCD dashboard |
| A pipeline in the tree | That pipeline's activity page |
| A run in the history pane | That run's value stream map |
| A stage in the details pane | That stage's detail page |
| A job, or the open job view | That job's detail page |
Triggering with t says so in the status line and names the key, so a run you just started is one keystroke from its page. Lowercase o still opens the GitHub commit, so the two never collide. Pipeline, stage, and job names arrive from the server and end up in a URL handed to your OS, so each one is validated against GoCD's own character set first; a name outside it reports in the status line instead of opening anything.
A command line, not just a UI
Not every question needs a full-screen interface. Naming a pipeline prints its latest run and exits, which is what you want from a shell, a script, or a watch:
The name is matched in tiers, so you rarely type it in full: an exact name wins, then a unique substring, then fzf-style initials (abt finds api-build-test). A query that matches several pipelines lists them and exits 1, so you narrow it instead of guessing which one you got. Shell completion fills the names in for you, read from the local dashboard cache rather than the network, so tab completion is instant and works offline.
--logs prints a job's console output and -f keeps tailing while the pipeline runs. It picks the job you probably meant: the one still running, else the one that failed, else the last job of the last stage. Override with --stage, --job and --run.
| Flag | What it does |
|---|---|
--logs -f | Print a job's console log, and keep tailing it while the job runs |
--history | List recent runs without the stage and job detail |
--json | The whole thing machine-readably, for jq |
-n | How many recent runs, or ambiguous matches, to list |
--run | Target a specific run counter, including ones older than the first history page |
Tailing is polling rather than a push stream, because GoCD serves console output as a plain file with a startLineNumber cursor and offers no websocket for clients. Most of the end-to-end lag is GoCD's own: the agent batches console output before shipping it, and measured against a live build, lines arrived four to ten seconds after their own timestamp. Log text passes through byte for byte so a build's ANSI colours survive a pipe, the header goes to stderr so --logs > build.log is only log, and under -f the exit status follows the stage, which makes it usable as a gate.
Cancel vs pause
The two are deliberately separate keys because they do different things:
| Key | What it does | Touches a running build? |
|---|---|---|
| p pause | Blocks future scheduling of the pipeline | No, a run already in flight keeps going |
| X cancel | Stops the currently running stage via POST /api/stages/.../cancel | Yes, that's its whole job |
X is only offered when the open pipeline's latest run actually has an active stage, and every mutating action (trigger, pause, unpause, cancel) sits behind a confirm prompt and is sent with the X-GoCD-Confirm header GoCD requires.
Favorites
Press f to star a pipeline. Starred pipelines pin to a gold ★ Favorites section at the top of the tree, expanded by default, and persist across sessions in ~/.config/lazygocd/favorites.json. A star also shows next to a favorited pipeline in its normal group listing, so you can tell at a glance from either place.
While a / filter is active the Favorites section hides itself, so search results stay a pure set with no duplicates.
The UI never blocks
All network calls run on background threads; the interface keeps responding while anything loads, with an animated spinner instead of frozen text. Input polling runs at 40ms (down from an earlier 150ms) so keypresses land instantly, and the dashboard plus the open pipeline's history silently re-poll every poll_interval_secs (default 30, configurable in config.toml).
A k9s-style stats bar keeps the fleet's health at a glance, and it only surfaces non-nominal states. Paused, building, and failed counts appear only when non-zero, so a healthy fleet shows a clean bar instead of a row of zeros.