Skip to main content

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 (like orun catalog refresh --push). Requires a configured backend and conflicts with --no-catalog-refresh.
  • Auto-sync — opt in with execution.state.autopushCatalog: true in intent.yaml (team-wide) or cloud.catalog.autopush: true in ~/.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 (or ORUN_VERBOSE is 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​

FlagMeaning
--intent, -iIntent file path (auto-discovered if not set)
--output, -oAlso write the plan to this path (optional; every plan is sealed into the object model under .orun/objectmodel/ regardless)
--format, -fOutput format: json or yaml
--nameA name shown in the plan summary. It is not a run reference: run a plan by its checksum or latest
--debugEnable debug logging during planning
--env, -eRestrict compilation to specific environments (comma-separated)
--all-envsCompile all environments explicitly (mutually exclusive with --env)
--componentRestrict compilation to one or more components (repeatable)
--view, -vRender a view such as dag, dag:long, dependencies, lanes, or component=<name>
--strict-lanesFail the plan when lanes would need a cycle split (as execution.lanes.onCycle: reject)
--max-lanesOverride 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
--weightsPacking 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-weightsWrite the weights measured from history to this file, the committed form for --weights
--longShow detailed output (step commands, IDs)
--changedEnable change-aware filtering
--baseBase git ref for change detection
--headHead git ref for change detection
--filesExplicit changed-file list
--uncommittedScope to uncommitted changes
--untrackedScope to untracked files
--explainPrint how --changed resolved its base and head refs
--intent-impactHow global intent changes affect components: all, watch (default), or none
--no-catalog-refreshSkip 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-catalogAfter 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-strictFail the plan on catalog resolution errors
--triggerNamed trigger binding for environment activation
--from-ciCI provider for event normalization (e.g. github)
--event-filePath to provider event JSON file
--artifactArtifact backend for uploading plan shard from CI (github)
--github-outputWrite 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.