Skip to main content

loom compile

Compile a Loom workflow into Graph IR v1 JSON or project a GitLab workflow into a value-safe plan report, without executing jobs.

Use repeatable --job <source-or-id> for a bounded graph containing those jobs and their complete ordering and artifact prerequisites. Both native Graph IR and the GitLab report expose optional job_selection with sorted requested_jobs, optional requested_instances, effective_jobs and a stable scope identity. This is partial-workflow evidence. Omitting the flag preserves full-workflow output. IDs are exact: empty and whitespace-padded IDs fail, commas are literal, and duplicates form one set. Unknown or unavailable selections fail without execution; rules still apply.

For integer fanout, --job shard selects available automatic source members before prerequisite closure. --instance 'shard 2/3' selects only that exact final node, even when its name is also a source alias. Repeat or combine both flags. job_selection binds both request sets and effective jobs into its identity. See integer fanout.

Canonical command usage​

Use the core CLI command reference for the exact loom compile synopsis, flags, and displayed defaults.

Output summary for GitLab sources​

For GitLab sources, stderr ends with one line, N executable jobs, M job decisions skipped, state <state> when an executable graph exists. N is its final node count, including selected fanout instances; M counts excluded, manual or unevaluated job decisions. Without a graph the summary explicitly reports job decisions selected/skipped instead. Stdout remains report JSON only, so jq pipelines are unaffected.

Workflow source and context​

Omitted flags fall back to the defaults block of the layered runtime config (see runtime config syntax); explicit flags always win.

Default discovery selects .loom/workflow.yml, then .loom/workflow.yaml, before the repository root .gitlab-ci.yml. --format loom|gitlab selects exactly one parser. Without --format, an explicit --workflow file is sniffed: a root version key or a job target means Loom; ordinary other mappings mean GitLab. An include-only mapping is ambiguous, and an explicit context envelope hints its frontend for indeterminate content, as described for loom check. Parser and read failures do not cause fallback; a Loom-schema failure on a GitLab-shaped file ends with this file looks like GitLab CI; pass --format gitlab.

Native --context accepts the strict caller variable envelope, including empty maps and values. Explicit format/content remains authoritative; configured defaults.context remains GitLab-only.

Use --context with GitLab sources to load a strict loom.gitlab-pipeline-context.v1 JSON document, by project-relative path or by short name (.loom/contexts/<name>.json, see loom context init). The file is project-relative, limited to 1 MiB, and keeps variables, changes, and runner_tags separate. changes.paths is optional: without it, changed paths are resolved from git as described for loom check and reported under resolved_changes. Ambient CI variables and runtime settings do not affect selection.

Workflow path trust boundary​

--workflow paths are resolved relative to the repository root. Absolute paths, paths containing .., and paths that resolve through escaping or dangling symlinks are rejected with WF_TRUSTED_PATH before workflow content is read. The same boundary applies to include.local.

Use cases​

  • Diff compiled plans across branches or commits to spot unexpected job changes
  • Verify include/template resolution — confirm extends and include.local merge correctly
  • Inspect the job graph (nodes + needs edges) before running
  • Feed Graph IR to external tools for visualization or analysis

Exit codes​

CodeMeaning
0Loom compilation succeeded, or GitLab plan is executable or skipped
1Source is invalid, GitLab plan is non-executable, or the command fails

Output streams​

StreamContent
stdoutGraph IR v1 JSON, or a GitLab plan report once a plan exists
stderrWarnings, diagnostics, and fatal errors

For GitLab, stdout uses loom.gitlab-plan-report.v1. It contains the plan schema and state, selection identity, selection records, requirements, diagnostics, and executable job topology. It excludes scripts, variables, and resolved context values. source_invalid and non_executable still write this report before returning nonzero; workflow_skipped writes a skipped report and returns 0.

Compile reports privileged-service requirements but cannot grant consent. It also exposes unsupported hosted macOS, OIDC token, Pages, and environment requirements; optional manual jobs remain visible but are not executed. Report processing and artifact expiry are diagnostic limitations.

Graph IR output format​

loom compile outputs a JSON object with these top-level keys:

KeyTypeDescription
schemastringIR schema identifier — currently loom.graph-ir.v1
stagesstring[]Ordered list of stage names
integer_fanoutobject[]Optional source groups with original totals and selected member IDs
nodesobject[]Executable jobs (template-only jobs are excluded)
edgesobject[]Dependency edges (currently: needs relationships)

Example output​

{
"schema": "loom.graph-ir.v1",
"stages": ["ci"],
"nodes": [
{
"id": "docs-lint",
"stage": "ci",
"target": "linux",
"image": "alpine:3.20",
"script": ["pnpm i --frozen-lockfile", "pnpm nx run loom-platform:docs-mdlint"],
"allow_failure": false,
"variables": [{ "key": "PNPM_STORE_DIR", "value": ".pnpm-store" }],
"cache": {
"paths": [".pnpm-store", ".nx/cache"],
"policy": "pull-push",
"when": "always",
"key": { "prefix": "loom-cache", "files": ["pnpm-lock.yaml"] }
}
}
],
"edges": []
}

Notes on node fields:

  • script must be present. An explicit empty array represents a zero-command main phase; missing or null is invalid. Source validation still determines which authored command forms are valid; native required scripts remain nonempty.
  • image, variables, and cache are omitted when not present or not effective for a node.
  • cache can be a single object or a list, matching the workflow's single-cache vs. multi-cache shape.
  • allow_failure is always a boolean and defaults to false. When true, only an ordinary nonzero exit from the main script may satisfy dependency ordering. The command result keeps its actual status, exit code, and reason. Setup, cancellation, timeout, provider, publication, finalization, and cleanup errors remain fatal.
  • Workflow source may declare allow_failure on jobs and template jobs. Resolved template values are emitted in Graph IR, and an explicit child value overrides its template.

Warnings​

Warnings are non-fatal. They are written to stderr in this format:

warning: <code> <path> <message>

Known warning codes​

CodeMeaning
WF_PLANNER_VARIABLE_NON_SCALARA variable value was not a scalar; it was ignored while building the Graph IR

Invalid needs targets, self-dependencies, and dependency cycles are compilation errors. They are reported at the offending needs source path and exit with code 1.

This list may grow. If you see an unknown warning code, include it when reporting issues.

GitLab diagnostics​

GitLab console diagnostics include every blocking finding and advisory findings that apply to the plan or automatically selected jobs. Blocking findings carry the error: prefix and advisory findings carry warning::

error: ci/.gitlab-ci.yml:8:9 GL_SOURCE_UNSUPPORTED: rule when is preserved but not executable yet [/job/rules/0/when]
hint: use when: on_success, never, manual or always, or omit when to keep the GitLab default
warning: ci/.gitlab-ci.yml:3:18 GL_LIMIT_INTERRUPTIBLE_IGNORED: GitLab interruptible cancellation is ignored locally [/job/interruptible]

Each line names the source path relative to the repository root, the line and column, the diagnostic code, the message, and the JSON pointer into the document. Diagnostics without a recorded source location fall back to CODE POINTER: MESSAGE. Findings with a known fix add an indented hint: line. Hints never appear in the stdout report, whose diagnostics entries keep the code, pointer, location, and message unchanged.

Diagnostics that share a code, message, and source location print once. A default: entry that GitLab applies to every job, such as interruptible: true, is reported at the default's location with the first eligible job's pointer and a count of eligible jobs instead of one line per job:

warning: .gitlab-ci.yml:67:18 GL_LIMIT_INTERRUPTIBLE_IGNORED: GitLab interruptible cancellation is ignored locally [/pnpm-deps/interruptible] (7 jobs)

The scope and count are presentation only. The stdout report retains all diagnostics entries, including findings for inactive jobs.

Examples​

Compile the default workflow​

loom compile

Compile a workflow at a custom path​

loom compile --workflow ci/staging-workflow.yml

Pipe Graph IR to jq for inspection​

loom compile | jq '.nodes[] | {id, stage, image}'

Diff compiled plans between branches​

diff <(git stash && loom compile) <(git stash pop && loom compile)

Reporting issues​

When loom compile fails or emits unexpected warnings, include:

  1. The command you ran (including --workflow if used)
  2. Warning or error lines from stderr
  3. The first ~20 lines of JSON output (redacted if needed)

See What to share for the full checklist.

  • loom check — validate schema without compiling
  • loom run — execute the compiled workflow

Native compile selection​

Use root/job selection with explicit caller context to filter native jobs before graph edges and --job closure:

loom compile --workflow .loom/conformance/compile-selection/native-host.yml --format loom --context .loom/conformance/compile-selection/caller-native-blue.json

This selects blue, common, raw-phase, scalar-logic and consumer. The same fixture with the green caller selects green in place of blue. Native output preserves optional compile_selection decision evidence through Graph IR JSON roundtrips. Compile inclusion does not bypass retained runtime rules.