orun plan
orun plan is the plan step, and the heaviest check before anything runs: it validates every component's parameters against its composition's schema, verifies any pinned source digest, applies inherited presets, rejects literal secrets, and refuses dependency cycles, then compiles the result into a deterministic plan.
orun plan compiles intent, component discovery, and compositions into an immutable execution DAG.
Usage
orun plan [component]
Pass a component name as a positional argument to restrict the plan to that component — equivalent to --component <name>:
orun plan network-foundation
When run without --intent, orun automatically discovers intent.yaml by walking up the directory tree to the git root. The plan is always global — it always includes all components. Use the positional argument or --component to explicitly restrict compilation to specific components.
Restrict environments with --env <list> (comma-separated), or make the all-environments default explicit with --all-envs (--env and --all-envs are mutually exclusive). A scoped plan is self-contained: dependency or promotion edges pointing at environments/components left out of the selection are pruned, reported as a warning, and recorded under metadata.selection.prunedEdges. The selection itself is stamped on the plan as metadata.selection (envs, components, mode, allEnvs).
Every successful compile produces an immutable PlanRevision — the pairing of
the compiled plan with the trigger occurrence that produced it — recorded in the
content-addressed object model under .orun/objectmodel/, with the
revisions/latest ref moved to point at it. orun run resolves the latest
revision automatically, so you rarely need to specify an output path. See
State model for the full layout.
Bare orun plan invocations resolve a system.manual trigger so the
revision/trigger chain is unbroken even for ad-hoc local runs.
Before compiling, orun plan refreshes the object-model catalog so change
detection and the cockpit see current component data. Use --no-catalog-refresh
to skip that step, or --catalog-strict to fail the plan when the catalog cannot
be resolved.
With --changed, the catalog snapshot that drives the selection is checked
against the component manifests in the working tree first. A stale snapshot is
rebuilt from the working tree, and a one-line notice
(orun: catalog was stale (…); refreshed from the working tree) is printed on
stderr when the component graph changed. With --no-catalog-refresh, the
snapshot is used as it is, and a stale one gets a warning that names
orun catalog refresh. See
Change detection.
Publishing the catalog after a plan
A successful plan can also publish the resolved catalog to the configured backend:
--push-catalog— explicit and fail-loud. Publishes the just-resolved catalog and advances the head (likeorun catalog refresh --push). Requires a configured backend and conflicts with--no-catalog-refresh.- Auto-sync — opt in with
execution.state.autopushCatalog: trueinintent.yaml(team-wide) orcloud.catalog.autopush: truein~/.orun/config.yaml(personal), and a successful plan best-effort publishes for you. Auto-sync only fires on the clean default branch, is debounced by catalog digest (an unchanged catalog is a no-op), never fails the plan, and is silent unless it actually pushes (orORUN_VERBOSEis set).
See configuration → remote state.
Common examples
Generate a plan (auto-discovers intent):
orun plan
Generate a plan scoped to one component (positional argument):
orun plan network-foundation
Generate a plan from a specific intent file:
orun plan -i examples/intent.yaml
Generate with an explicit output path (for backwards compatibility):
orun plan -i examples/intent.yaml -o /tmp/orun-plan.json
Label a plan in its summary (run it later by its checksum or latest, not by this name):
orun plan -i examples/intent.yaml --name release-candidate
Generate YAML output:
orun plan -i examples/intent.yaml -o plan.yaml -f yaml
Filter to one environment:
orun plan -i examples/intent.yaml --env staging
Filter to one component:
orun plan -i examples/intent.yaml --component api-edge-worker
Preview the dependency graph while compiling:
orun plan -i examples/intent.yaml --view dag
Focus on changed components:
orun plan -i examples/intent.yaml --changed --base main
Since v2.9.0 --changed only includes components whose files
actually changed. Dependencies are pulled in only when their edge
declares include: always — see
Dependency rules → Include policy.
Debug how --changed resolved its git refs:
orun plan -i examples/intent.yaml --changed --explain
Use trigger bindings for CI-driven environment activation:
orun plan --from-ci github --event-file "$GITHUB_EVENT_PATH"
Simulate a named trigger locally:
orun plan --trigger github-pull-request --base main --head HEAD
A trigger with plan.scope: selected (for example one bound to
workflow_dispatch) plans exactly the components and environments its event
names; --changed and --files are ignored for that plan. Under --trigger
there is no event, so --env and --component are the selection:
orun plan --trigger github-dispatch --env stage --component api
See Trigger bindings → Selected plans.
Flags
| Flag | Meaning |
|---|---|
--intent, -i | Intent file path (auto-discovered if not set) |
--output, -o | Also write the plan to this path (optional; every plan is sealed into the object model under .orun/objectmodel/ regardless) |
--format, -f | Output format: json or yaml |
--name | A name shown in the plan summary. It is not a run reference: run a plan by its checksum or latest |
--debug | Enable debug logging during planning |
--env, -e | Restrict compilation to specific environments (comma-separated) |
--all-envs | Compile all environments explicitly (mutually exclusive with --env) |
--component | Restrict compilation to one or more components (repeatable) |
--view, -v | Render a view such as dag, dag:long, dependencies, lanes, or component=<name> |
--strict-lanes | Fail the plan when lanes would need a cycle split (as execution.lanes.onCycle: reject) |
--max-lanes | Override execution.lanes.maxLanes for this plan, for example from the runner pool's free capacity (ORUN_MAX_LANES does the same; the flag wins). Recorded in metadata.lanes |
--weights | Packing weights for auto-mode lanes: history (the median duration of each job over the last ten sealed runs) or a JSON file of job id to seconds. Unset: the templates' resources.estimatedSeconds, else every job weighs one. See Weights |
--write-weights | Write the weights measured from history to this file, the committed form for --weights |
--long | Show detailed output (step commands, IDs) |
--changed | Enable change-aware filtering |
--base | Base git ref for change detection |
--head | Head git ref for change detection |
--files | Explicit changed-file list |
--uncommitted | Scope to uncommitted changes |
--untracked | Scope to untracked files |
--explain | Print how --changed resolved its base and head refs |
--intent-impact | How global intent changes affect components: all, watch (default), or none |
--no-catalog-refresh | Skip the pre-plan catalog refresh; plan without catalog context. With --changed, use the existing snapshot as it is and warn if it is stale |
--push-catalog | After planning, sync the resolved catalog snapshot to the configured backend and advance the head (like catalog refresh --push). Requires a configured backend; conflicts with --no-catalog-refresh |
--catalog-strict | Fail the plan on catalog resolution errors |
--trigger | Named trigger binding for environment activation |
--from-ci | CI provider for event normalization (e.g. github) |
--event-file | Path to provider event JSON file |
--artifact | Artifact backend for uploading plan shard from CI (github) |
--github-output | Write matrix, lane-matrix, lane-count, lane-matrix-<k> per level, lane-matrix-levels, lane-level-count, plan_id, exec_id to $GITHUB_OUTPUT (for GitHub Actions matrix) |
GitHub Actions usage
--github-output appends the step outputs a workflow fans out on to the file
$GITHUB_OUTPUT names: matrix, lane-matrix, lane-count,
lane-matrix-<k> for each level k, lane-matrix-levels,
lane-level-count, plan_id and exec_id. lane-matrix has one entry per
lane, or one per job on a plan without lanes, each with its level and
upstreamLanes; run each with orun run --lane ${{ matrix.lane }}.
lane-matrix-<k> holds the lanes of level k, so a workflow can run the
levels as chained matrix jobs (see
Lanes). exec_id is derived from
GITHUB_RUN_ID, GITHUB_RUN_ATTEMPT and the plan checksum.
The outputs need nothing but $GITHUB_OUTPUT: a workflow whose run jobs take
the plan from the platform (orun run --remote-state) passes no --artifact.
When $GITHUB_OUTPUT is not set, the plan succeeds and warns that nothing was
written.
orun plan --from-ci github --event-file "$GITHUB_EVENT_PATH" --github-output
With --artifact github, orun plan also writes a plan shard (manifest.json +
plan.json + metadata) to a temp directory and uploads it as a GitHub Actions
artifact named by the same exec_id, using the embedded @actions/artifact
helper, for run jobs that download the plan with orun run --artifact github:
orun plan \
--from-ci github \
--event-file "$GITHUB_EVENT_PATH" \
--artifact github \
--github-output
This replaces the need for actions/upload-artifact@v4 and manual jq piping in CI workflows.
Output contract
The generated plan contains explicit jobs, dependency edges, step phases, labels, and runtime metadata. Read plan schema for the full structure.
Recorded plans can be inspected with orun get plans and orun describe plan <id>, which read the plan nodes from the object model.
Use --config-dir only when you need to load legacy folder-shaped compositions instead of intent-declared packages.
See context-aware discovery for how orun run and orun get jobs auto-filter by component when run from inside a component directory.