CI/CD Commands Cheatsheet - CI/CD Pipeline Debugging Reference
When a CI job sits pending or fails for no clear reason, the reflex is to edit YAML and push again, which is both slow and rarely the cause. A better order exists: confirm the runner is online, that its labels match what the job requested, and that concurrency quota is not exhausted; then read the actual log of the failing step rather than its last line; then decide whether you can reproduce locally and eliminate environment differences one at a time. Low cache hit rates usually come from a key missing the lockfile hash or a path that does not cover the real dependency directory, both diagnosable from configuration alone without restructuring the pipeline. Commands for GitHub Actions, GitLab CI and Jenkins are listed in separate sections, alongside Docker multi-stage builds and general practices. Hung jobs point at infrastructure state; failed jobs point at logs. Most investigations end at one of three places, and naming which one you are in keeps the search short.
GitHub Actions 6
gh run list --limit 10gh run view <run-id> --log-failedgh run rerun <run-id> --failedgh run cancel <run-id>gh workflow run deploy.yml --ref maingh api repos/:owner/:repo/actions/runnersGitLab CI 6
gitlab-runner listgitlab-runner verifygitlab-runner rungitlab-ci-lint < .gitlab-ci.ymlglab ci list --per-page 20curl -H "PRIVATE-TOKEN: <token>" "https://gitlab/api/v4/projects/:id/pipelines"Jenkins 5
jenkins-cli build <job> -f -vjenkins-cli console <job> <build>systemctl status jenkinsls -lah /var/lib/jenkins/jobs/<job>/builds/jenkins-cli list-jobsDocker CI Builds 5
docker build -t app:ci --target build .docker build --cache-from=app:base .docker tag app:ci registry/app:$(git rev-parse --short HEAD)docker buildx build --platform linux/amd64,linux/arm64 .docker history app:ci --no-truncRunner & Cache Troubleshooting 5
docker ps --filter "name=runner"df -h /var/lib/dockeract -j <job> -W .github/workflows/ci.ymlgitlab-runner exec docker <job>du -sh ~/.cache/pnpm /root/.cacheCommon Patterns & Best Practices 6
echo "key=value" >> $GITHUB_OUTPUT${{ secrets.NAME }} / $CI_NAMEneeds: [build] / depends_on: [test]workflow_dispatch: / schedule: / push:rules: / only: / except:artifacts: paths: / expire_in: 1 weekCommand Examples
Locate the latest failed pipeline and pull just the failing step logs
gh run list --limit 10
gh run view <run-id> --log-failedConfirm the run ID and blast radius in the list first, then fetch only the failing step output — far quicker than opening each job, and it makes the offending command obvious.
Output
STATUS NAME WORKFLOW BRANCH EVENT ID ELAPSED AGE failure CI / build ci.yml main push 1287349921 4m12s 18m === Failed steps === Run gh run view 1287349921 --log-failed Run webapp (build) 4: Run npm ci Error: Process completed with exit code 1.
Re-run only the failed jobs, leaving successful ones untouched
gh run rerun <run-id> --failedSuccessful jobs keep their artefacts and cache layers, so the rerun is quick and the signal is clean: only the failed job changed, so the difference is your fix. If the failed job genuinely needs freshly generated upstream output, fall back to a full re-run.
Trigger a deploy on demand instead of waiting for the next push
gh workflow run deploy.yml --ref mainThe workflow must declare the workflow_dispatch trigger or this command produces no run at all. Pass --ref to pin the branch so a multi-branch repository cannot pick the wrong one.
Runner triage: online state, disk headroom, container liveness
gh api repos/:owner/:repo/actions/runners
docker ps --filter "name=runner"
df -h /var/lib/dockerTogether these three explain most unexplained pending states: an offline runner, a label mismatch, or a container host whose disk is full so image pulls fail. When df approaches 100 percent usage, clear images and stale build layers first.
Output
{
"total_count": 2,
"runners": [
{ "name": "ci-runner-01", "os": "Linux", "status": "online", "busy": false, "labels": [{"name": "self-hosted"}] },
{ "name": "ci-runner-02", "os": "Linux", "status": "offline", "busy": false, "labels": [{"name": "self-hosted"}] }
]
}
CONTAINER ID IMAGE STATUS NAMES
3f9c1a2b7d10 ci-runner:2024 Up 6 hours runner-controller
Filesystem Size Used Avail Use% Mounted on
/dev/vdb1 100G 98G 2.0G 99% /var/lib/dockerCommon Pitfalls
- CI variables come in protected and masked flavours; sensitive values need both an environment restriction and masking enabled. Protection alone still allows the value to appear in logs.
- CI logs are retained for a long time, so any token echoed or printed is already leaked and deleting the log afterwards does nothing — revoke and rotate the credential instead.
- When reproducing locally with act or gitlab-runner exec, your image, shell and toolchain versions differ from the hosted environment. A green local run is not evidence that the hosted run will pass, so do not merge on that basis.
- A multi-platform docker buildx build only takes effect once the result is pushed to a registry; a local --load cannot hold a multi-arch image, so omitting the push flag wastes the build.
- Declaring needs or depends_on controls both execution order and artefact availability. Leaving one out makes a downstream job silently miss upstream artefacts rather than fail loudly.
Tips
- If a job stays pending, check runner online status and label matching first, then concurrency limits — not by repeatedly editing YAML.
- When cache hit rate is low, check whether the key uses a lockfile hash and whether the path covers the real dependency directory.
- CI logs may be retained long-term; never echo secrets or tokens while debugging.
- Multi-stage Docker builds separate build dependencies from the runtime, greatly shrinking the final image.
- CI variables come in protected and masked types; always mark sensitive values masked to prevent log leakage.
- Reproduce CI locally with act (GitHub Actions) or gitlab-runner exec instead of pushing repeatedly.
FAQ
A job sits pending forever. Why is editing the YAML the wrong first move?
Pending means the scheduler never dispatched the job, so the YAML has not had a chance to run at all. Establish first that a runner is online and that its labels satisfy what the job requests, then check whether the concurrency allowance is exhausted; for containerised runners use docker ps --filter "name=runner" to confirm the container was not evicted. Infrastructure conditions like labels or quota will not improve no matter how many times you rewrite the pipeline file.
Only some jobs failed. Why prefer rerun --failed over a full re-run?
A full re-run throws away the part that already succeeded, which is usually the expensive half. gh run rerun <run-id> --failed re-runs only the failed jobs while successful ones keep their artefacts and cached layers, making the rerun faster and making it obvious whether your fix actually worked. The caveat is that nothing upstream is recomputed, so use this mode only when the successful jobs genuinely produced reusable output.
Why is the cache hit rate low?
Almost always it is a question of key granularity and path scope. Put the hash of a lockfile or manifest in the key rather than the branch name, otherwise any source edit invalidates the whole cache; and make sure the path list covers the real dependency directory — a correct key with a path pointing at a subdirectory still yields an empty hit. Verify by checking whether the lockfile actually changed between two runs, then confirm the configured path matches where dependencies really land.
The pipeline will not reproduce locally. What should I confirm first?
Confirm the local tool is reading the same configuration you think it is. act -j <job> -W .github/workflows/ci.yml names the workflow file explicitly, and gitlab-runner exec docker <job> demands that the local registration matches the hosted runner — mismatches usually fail loudly rather than drifting silently. Once the job passes locally but still fails hosted, the remaining difference is almost always environmental: OS-level packages baked into the image, environment variables, and reachability of downstream services.
Why must secrets never be echoed into CI logs?
CI logs have a far wider audience than most people assume: failed-step output is retained for a long time, anyone with repo or build access can read it, and once a value lands in a log it is already leaked — deleting the line afterwards does not undo it. Keep sensitive values in masked variables, and when you must debug, print the variable name or a few leading characters rather than the value.
Official References
Each command links to its official documentation below, so you can verify the latest usage and read deeper.
Maintained by LaoHand
Publicly updated on Jul 21, 2026, continuously proofread against official docs.
Contact Us
Wrong command or description? Send us corrections, business inquiries or product feedback by email.
Contact Us