Acting on it
Every action below starts something you committed, running on your executor. behold holds no credentials and performs no cloud write.
The actions, and where they come from
Section titled “The actions, and where they come from”| action | where | starts | direction |
|---|---|---|---|
| ▶ Deploy (<op>) | the control panel’s Deploy tab, and ⌘K as “Deploy: Sync” | your ApplyOp |
code → cloud |
| ▶ Deploy… | Deploy tab (no ApplyOp, but an env) |
chant run <component|all> --components, via the dial’s picker |
code → cloud |
| Approve <gate> | Deploy tab beside Deploy, and ⌘K | the gate signal on your ApplyOp |
— |
| Adopt | per foreign node, in the inspect panel | your ReconcileOp |
cloud → code, as a PR |
| Run: <name> | ⌘K palette | any other committed Op | whatever you wrote |
| Rollback… | ⌘K palette | a rollback PR to a picked commit | code → code |
The floating control panel’s Deploy tab carries a single ▶ Deploy button whether the project routes it through a committed ApplyOp or a raw component apply; the tooltip says which. Every action is also in the ⌘K palette. A project with no Ops and no environment shows no deploy affordance, by design.
A destructive apply pauses for approval. behold surfaces the gate and the Approve signal; the workflow is durable, so the pause survives a restart of anything, including behold.
Auto-sync never approves a gate. That is the invariant that makes an automatic loop acceptable at all.
Deploying through a forge
Section titled “Deploying through a forge”An environment can be designated to deploy through GitHub Actions rather than from the machine behold runs on. In the project’s .behold.json:
{ "executor": { "prod": { "forge": "github", "workflow": "deploy-prod.yml" } } }The designation names the committed workflow file rather than only the forge, because two environments’ generated pipelines carry identical job ids and nothing short of the file tells prod’s from staging’s. For a designated environment:
- Deploy dispatches that workflow through your own
ghlogin (gh workflow run, on the current branch) and follows the run on the dial, job by job, to GitHub’s own verdict. The run’s page is linked from the dial. - A local apply, a committed ApplyOp for that environment, and auto-sync are refused with
executor-forge, and rollback is withheld. Nothing about that environment ever runs here. - Approval lives on the forge. If the workflow’s GitHub environment has a protection rule, the approval is granted there, by your GitHub identity. behold holds none that could, on purpose, so the only affordance it offers is the link.
- A designation behold cannot honour (a forge it has no trigger for, a workflow that is missing or lacks
workflow_dispatch) disables Deploy for that environment and says why. It never falls back to a guess, and never to your laptop.
Without a designation, the palette’s dispatch picks the committed workflow named for the environment (chant-components-<env>, as chant 0.54 names a generated pipeline). On older projects it matches by job ids, refuses a tie rather than letting directory order choose, and never picks a workflow named for another environment.
A dispatched run’s id is persisted under ~/.behold/ci-runs/, the operator’s own state. Restart behold mid-deploy and it re-adopts the run at boot; POST /api/ci/readopt does the same on request. A follow whose polls keep failing, or that outlives its deadline, is reported as lost rather than as a verdict. The run itself may still be live at its page, which the lost line names.
Auto-sync
Section titled “Auto-sync”Off by default. With --poll and --auto-sync, a detected drift triggers a committed Op:
npx behold serve . --env prod --poll 30 --auto-sync applyapplyheals the cloud toward source, via yourApplyOp.pull-requestadopts live into source, via yourReconcileOp, as a PR someone merges.
It routes per substrate: drift in the Kubernetes half triggers the Op that owns Kubernetes rather than whichever Op sorts first. Where several Ops match a substrate, or none does, it declines and says so on the now-line. A self-heal loop that picks the wrong half of an estate to rewrite is worse than one that stops.
While a rollback PR is open, pull-request mode suspends. Otherwise the loop would re-adopt exactly what the rollback removed, and the two would fight.
Rollback
Section titled “Rollback”Rollback opens a PR restoring source to a prior revision. Merging it moves source; the estate follows on the next apply. behold never force-reverts a cloud.