Debugging & Inspection
Variable precedence debugging#
When a job behaves unexpectedly, the variable stack is often the cause. See Variables & Secrets for the full precedence table.
Start by listing what each job actually resolves to and where each value came from:
glci variables my-job # job/rules vars + per-rule evaluation trace
glci variables my-job --all # also include predefined CI_* variables
glci run --show-variables my-job # fully resolved set (incl. secrets) at run time
glci variables resolves locally without running, so it also shows jobs that rules: excluded — see Inspecting resolved variables. To isolate which variable source is causing a problem:
glci run --secrets none my-job # run without remote variables
glci run --secrets project my-job # project-level only (skip group)
glci run --env MY_VAR=debug my-job # override a specific variable
glci run --refresh-secrets my-job # force refresh cached API variables
Debugging rule evaluation#
Rules evaluation works largely the same as production GitLab CI (docs), with one intentional difference for exists: noted below. glci supports if:, changes: (with compare_to:), and exists: conditions. Both changes: and exists: accept ** globs that match at any depth (e.g. exists: ['**/*.php'] matches .php files in the project root and any subdirectory) and brace expansion ({a,b}, e.g. ['*.{php,inc}']), mirroring GitLab’s FNM_EXTGLOB. exists: matches regular files only, never directories – though a trailing slash (exists: ['src/']) is a directory-presence check that matches when any file lives under that directory. See workflow rules for the full glob reference.
exists: evaluates the project’s non-ignored files — everything git tracks plus untracked files, but excluding anything matched by .gitignore (git ls-files --cached --others --exclude-standard). This mirrors glci’s dirty mode, which sends your working tree (minus gitignored files) to the runner, so an exists: rule sees the same files your job will. Build artifacts such as node_modules/, target/, and other gitignored output therefore do not satisfy an exists: rule, while files you have created but not yet committed do.
Difference from GitLab: GitLab evaluates
exists:against the committed tree at the pipeline SHA (tracked files only). glci evaluates your local working tree minus gitignored files, so uncommitted/untracked files match locally where they would not on GitLab until committed. When the project directory is not a git repository, glci falls back to scanning the entire working tree (gitignore can’t be consulted).
Like GitLab, exists: glob matching has a comparison budget (50,000 path×glob comparisons). On a project large enough to exceed it, glci — like GitLab — assumes a match (fails open) rather than risk dropping a job, and prints a warning. Literal paths and **/*.ext extension globs are matched directly and are not subject to the budget.
See exactly which rule matched (or why none did) for a job, including the variable values each condition was evaluated against:
glci variables my-job # per-rule trace + resolved variables
glci variables my-job --context branch=main
Compare the pipeline across contexts to understand which rules matched:
glci jobs --context merge_request
glci jobs --context branch=main
diff <(glci show --json --context merge_request) \
<(glci show --json --context branch=main)
If glci show returns no jobs, workflow: rules: may be rejecting the entire pipeline. Try a different context.
Daemon logs#
The glci daemon writes logs to ~/.glci/daemon.log with details not visible in job output: mock server requests, runner container lifecycle, child pipeline events, cross-project trigger resolution, and scheduler decisions.
tail -f ~/.glci/daemon.log # follow in real time
glci system logs info # check file size
glci system logs clean # clear the log file (safe while daemon runs)
Log lines are prefixed with their source: daemon: (orchestration), mock-server: (API handling), runner: (gitlab-runner output).
Job logs#
glci log # all logs from latest pipeline
glci log 5 build-job # specific job from pipeline #5
glci history # find child pipeline IDs
glci log 7 child-job-name # child pipeline job log
glci log streams live output for running jobs and reads from disk for completed jobs.
Common issues#
Job is unexpectedly skipped#
Check which context is active (default is merge_request). Compare glci jobs --context merge_request vs glci jobs --context branch=main. If the job appears in one but not the other, the issue is in rules:. Also check workflow: rules: which can reject the entire pipeline.
Job fails with “variable not found”#
Isolate the source: glci run --secrets none my-job (remove remote vars), then glci run --secrets all my-job (add them back). Override with --env MISSING_VAR=value to confirm.
Trigger job fails immediately#
- Child pipelines: verify the include file exists on disk.
- Cross-project triggers: ensure
--project-dir group/project=../projectis set. - Check
tail -20 ~/.glci/daemon.logfor resolution errors.
Include resolution fails#
include: project:requires a GitLab token – runglci doctorto verify.include: component:with version selectors (@~latest,@~N,@~N.M) requires a GitLab token to query the Tags API. If the token is missing or lacks read access, you will see errors likefailed to resolve version selector: unauthorizedorno matching tags found for selector ~latest.- Max nesting depth is 10 levels.
Tips#
- Quick validation loop:
glci show --watchre-renders the pipeline graph on every save. - Environment health check:
glci doctorchecks Docker, daemon, token, CI config, and git. - Simulation mode:
glci run --simulatereplaces scripts with echo commands and produces dummy artifacts. - Effective config:
glci config,glci config --network,glci config --gitlab.