loha plan
loha plan shows what Loha would run for your .loha.yml on a given branch and event:
which jobs, which steps, which images, how much CPU and memory, and which secrets each
step may read. Nothing runs. No build is created, no secret is decrypted and no
token is minted, so a plan is safe to run as often as you like.
It is the same assembly the cluster uses to start a real build, read from the Loha server, so the plan matches the build page and not your local reading of the file. The same inputs that a real run refuses (an image outside the allowed registries, a denied environment name, a matrix over its limit) are refused by the plan with the same message.
loha plan is part of the local-run feature, which is on by default. If a Loha platform
administrator has switched it off for your workspace, the command stops with
Local runs are not enabled for this workspace. Ask a workspace admin to turn on local_run.
and exits with code 2. To run the plan on your own machine, see
loha run --local.
Usage
Run it from inside a checkout whose origin remote is a repository onboarded to Loha:
loha plan
Without flags the CLI reads the branch, the head commit and the repository from your
git checkout, assumes a push event, and plans ./.loha.yml.
Plan for .loha.yml on main (push) at 3f9c2ab
On: would fire (Matches the on.push entry for branch main.)
Trigger: tr_8a1 (auto)
build [medium 2 / 4Gi]
run clone node:22
emu checkout alpine/git@sha256:aaaa…
↳ Replaced by the staged working tree.
run test node:22
secrets: NPM_TOKEN
skip lint-extra node:22
↳ Condition `matrix.skip_lint != 'true'` is false for branch main, event push.
release skipped: Condition `github.event == 'tag_push'` is false for branch main, event push.
loha-security-final-gate [platform]
skip security-gate
↳ The managed security gate runs only on the cluster.
! Step "notify" references secret "SLACK_URL" but no such secret is defined in scope
Variables: names only (token lacks variables:values:read)
The values above are examples. Image names, ids and the jobs you see depend on your
workspace and pipeline. Blank lines separate the header, each job, the warnings and the
Variables: line.
Flags
| Flag | Purpose |
|---|---|
--file <path> | Pipeline file to plan (default .loha.yml) |
--branch <name> | Branch to plan for (default: the checked-out branch; a detached HEAD needs this flag) |
--event <type> | Event to plan for: push (default), pull_request, tag, manual or release (tag is sent as tag_push, which is also accepted). Job and step conditions are evaluated against it |
--base <branch> | Target branch of the pull request, to evaluate the on: block for it. Only with --event pull_request |
--tag <tag> | The tag that was pushed, to evaluate the on: block for it. Only with --event tag |
--sha <sha> | Commit to plan for (default: HEAD) |
--repo <id> | Repository to plan against (default: found from the git remote) |
--trigger <id> | Use this trigger's variables and settings |
--var KEY=VALUE | Runtime variable injected as step env, repeatable |
--json | Print the server's plan unchanged, for scripts and agents |
Exit codes
| Code | Meaning |
|---|---|
0 | The plan was rendered |
1 | The configuration was rejected; the server's errors are printed exactly as the cluster would give them |
2 | Usage or authentication error, local runs not enabled for the workspace, a request the server could not read (for example a malformed --sha), a stray argument, or server trouble (unknown repository or trigger, planner unavailable, rate limit, 5xx) |
Reading a plan
Header. Plan for <file> on <branch> (<event>) at <short sha>. For --event tag the event shows as tag_push.
On. On: would fire (<reason>) or On: would not fire (<reason>) says whether the
pipeline's on: block would start a build for this event, branch, and (with --base or
--tag) pull request target or tag, the server's answer to the same check a real push
makes. Pass --event pull_request --base main or --event tag --tag v1.2.3 to ask about
those events. For a release event the line reads On: not evaluated for release events.
A server that does not report it leaves the line out. It is information only: a plan is
rendered, and loha run --local runs, either way (a local run prints
the same line first, as a notice: ! On: would fire (<reason>) or
! On: would not fire (<reason>)). --base with an event other than
pull_request, or --tag with one other than tag, is a usage error (exit 2), so the
answer never means something else than what you asked.
Trigger. Trigger: <id> (explicit) when you passed --trigger, and
Trigger: <id> (auto) when exactly one enabled trigger on the repository reads this
file and would fire for the event. Otherwise Trigger: none, and a warning lists the
candidates so you can choose one with --trigger. A trigger matters because
trigger-scoped variables and plan-level resource defaults
come from it.
Jobs. Each job prints as <id> [<class> <cpu> / <memory>], the resources the
cluster would give it. A job that would not run prints <id> skipped: <reason> (for
example its condition is false for this branch). Platform jobs that Loha adds itself, such as the security gate, print [platform]
without CPU or memory. Matrix jobs appear already expanded, one entry per combination,
exactly as on the build page.
Steps. Every step starts with a badge:
| Badge | Meaning |
|---|---|
run | Runs as written: same image, same script. loha run --local executes it unchanged. |
emu | Emulated: the cluster step relies on something a laptop does not have, so a local run substitutes it: a clone of the pushed commit, the cache, job outputs and artifacts (a local server answers the real scripts), an image build (your own engine builds it, nothing is pushed) or an approval gate (you answer in the terminal). |
skip | Not run on your machine. Either it changes remote state (push, deploy, notify, signing), or it is the platform security gate, or its if: is false for this branch and event. The reason is printed under the step. |
The badge describes what a local run would do, not what the cluster does: for
example a deploy step marked skip still deploys on the cluster. A line starting with ↳ under a step gives the reason, and
secrets: A, B lists the secret names the step was granted.
Warnings. Lines starting with ! are things the plan could not decide for you,
for example a step that names a secret which does not exist in scope, or a quota that
would delay the build. Warnings do not make the plan fail.
Variables. The last line tells you whether stored variable values are in the plan, see below.
Variables and secrets in a plan
A plan never contains a secret value. For each step it lists only the names of the secrets it was granted and that exist in scope.
Plain (non-secret) variables follow your token:
| Your token | The plan shows |
|---|---|
has variables:values:read | Plain variable values (Variables: values included) |
| does not | Names only (Variables: names only (token lacks variables:values:read)) |
variables:values:read is an opt-in personal access token scope.
variables:read keeps its meaning (metadata, never values), guests cannot hold the new
scope, and the "Read only" preset does not include it. Tokens created before it existed
do not gain it; create a new token and select it if you want values in your plans.
A plan needs the pipelines:read scope and, like every workspace token, can only see
the workspace it is bound to. A service account key bound to one project can only plan against that
project's repositories.
No matching repository
If the git remote does not match a repository in the active workspace, the plan is
rendered without one and says so:
No connected repository matches this checkout; planning without repository or trigger variables.
Repository- and trigger-scoped variables are then absent from the plan. Pass --repo
to name the repository yourself.
What a plan does and does not reproduce
A plan is the cluster's own answer, so it covers everything the server decides. The following lists what is the same on a laptop, what would be emulated, and what is different, for the day steps run locally.
| Aspect | Local vs cluster |
|---|---|
| Parsing, conditions, matrix, env precedence, task rendering, image policy, denied env names | Same: all decided by the server |
| Step images, scripts, user id, capabilities, working directory | Same |
| CPU and memory | Same values; scheduling and node fit are not reproduced |
| Source | Emulated: your working tree instead of a clone of the pushed commit |
| Services | Emulated: container network aliases instead of sidecars |
| Cache, job outputs, image builds, approval gates | Emulated: see loha run --local for how each one differs |
| Network | Different: your network, not build-pod egress rules or cluster mirrors |
| Architecture | Different unless a platform is chosen |
| Secrets, stored variables | Different source: secrets are never in the plan; plain variables only with the opt-in scope |
| Push, deploy, notify, signing, security gate, build record | Never locally |
API
loha plan calls POST /api/v1/pipelines/plan (scope pipelines:read). The body
carries the YAML and the git context (yaml, branch, event, commitSha, and
optionally repositoryId, triggerId, runtimeVars, targetBranch, tag); the response is { "plan": … }.
An invalid document returns 422 with the same error list as
loha validate; a request the server cannot read (for example a malformed commitSha) returns 400
with code: "VALIDATION_ERROR" and the field messages; a configuration the cluster would refuse returns 400 with
its code and message (PIPELINE_CONFIG_ERROR); a Build Security refusal
(for example IMMUTABLE_SOURCE_REQUIRED) or a configuration the plan cannot render
(PLAN_PROJECTION_FAILED) returns 422 with its code and message; an unknown repository or trigger gets 404
(Repository not found or Trigger not found); a service account key bound to another
project, or planning without a repository, gets 403 (Service account is not bound to this project); a workspace without the feature gets 403 with
code: "FEATURE_DISABLED"; and any planner failure (the planner is down, or answers
something other than a refusal of your request) gets 503 with
code: "CONTROLLER_UNAVAILABLE" (The pipeline planner is unavailable. Try again shortly.). The full schema is in the
OpenAPI document.
See also
loha run --local: run the plan in containers on your machine- CLI: install, login and the other commands
- Personal access tokens: scopes, including
variables:values:read - Triggers