Feedback from a real production deploy (pursue → v2026.07.10) driven end-to-end through ob. The tool is genuinely strong — the plan output (risk/reversibility/approval, pinned digests, exact remote commands) and the deploy trace (preflight → transfer → migrate → rolling drain/converge → verify → activate, with a rollback hint) are clear and confidence-inspiring. These are refinements, most-valuable first.
1. Strong-approval path isn't automatable, and -y is misleading
ob deploy -y is documented as "skip the confirmation prompt" (cmd/ob/commands.go:203), but a strong plan still hard-fails without a grant:
✗ ob: strong approval is required for this exact deployment plan; create a bound grant with ob approve --plan PLAN and apply it with ob deploy --plan PLAN --approval APPROVAL
So -y reads as "this will deploy" when it won't (see the structured-deploy guard at cmd/ob/commands.go:609,629). Worse, ob approve exposes only --plan and --out (cmd/ob/commands.go:177-188) — there is no non-interactive confirmation flag. The only way to approve from a non-TTY (CI, an agent, a script) is to pipe the exact release ID into the interactive prompt:
printf '%s\n' "$RELEASE_ID" | ob approve --plan plan.json -o approval.json
That's fragile and clearly not an intended interface.
Suggested:
- Add a first-class non-interactive approve, e.g.
ob approve --plan PLAN --confirm <release-id> (require the ID as an explicit arg so it stays a deliberate act, not a blanket --yes).
- Clarify
-y's help: it does not satisfy strong approval. Ideally ob deploy -y on a strong plan fails fast stating that (it already points to approve+approval, which is good).
- Consumers wiring
ob into just/CI hit this immediately: a just deploy that runs ob deploy --plan … always fails on strong plans because there's no scriptable approve step to put in front of it.
2. A no-op migration downgrades rollback safety to changed=unknown
This deploy touched no schema, and the plan correctly showed job:migrate … changed=false. But at execution:
⚠ migration job migrate: changed=unknown (result file is missing); automatic rollback is unavailable after this step
The gate mounts a result file and reads it back (internal/engine/gate.go:112-165); when the job doesn't write OB_RESULT_FILE, it falls to reason = "result file is missing" → changed=unknown (internal/engine/gate.go:171,202). The stock atlas migrate image doesn't write that file on a clean no-op, so a zero-change deploy silently loses automatic rollback.
Suggested: treat a clean migrate exit with no diff as changed=false (keep rollback open), or have the bundled migrate wrapper always write a result on success. A no-op migration shouldn't be scarier than a real one.
3. Tight, shared plan/approval expiry
The bound plan expired ~15 min after generation (expires: 2026-07-14T21:34:51Z), and plan → approve → deploy all share that window. For a flow whose whole point is a human approval pause, 15 min is short. A longer default (or a visible countdown / "regenerate" hint on expiry) would cut down on plan-regeneration churn.
4. Artifact hygiene
Plan and approval artifacts land in CWD (ob approve defaults --out ob-approval.json, cmd/ob/commands.go:186). Over a few releases the repo root accumulates ob-plan-*.json and now ob-approval-*.json (the grant is correctly 0600). Consider defaulting these under an XDG/state dir, plus an ob prune for spent plans and consumed grants.
5. Minor: dirty build in a release tool
The runner self-reports ob 0.0.1-m0 (…+dirty). A tool deploying production from a dirty build is a smell — consider warning (or refusing without --force).
Happy to send a PR for #1 (non-interactive ob approve --confirm) if that direction sounds right.
Feedback from a real production deploy (
pursue→ v2026.07.10) driven end-to-end throughob. The tool is genuinely strong — the plan output (risk/reversibility/approval, pinned digests, exact remote commands) and the deploy trace (preflight → transfer → migrate → rolling drain/converge → verify → activate, with a rollback hint) are clear and confidence-inspiring. These are refinements, most-valuable first.1. Strong-approval path isn't automatable, and
-yis misleadingob deploy -yis documented as "skip the confirmation prompt" (cmd/ob/commands.go:203), but a strong plan still hard-fails without a grant:So
-yreads as "this will deploy" when it won't (see the structured-deploy guard atcmd/ob/commands.go:609,629). Worse,ob approveexposes only--planand--out(cmd/ob/commands.go:177-188) — there is no non-interactive confirmation flag. The only way to approve from a non-TTY (CI, an agent, a script) is to pipe the exact release ID into the interactive prompt:That's fragile and clearly not an intended interface.
Suggested:
ob approve --plan PLAN --confirm <release-id>(require the ID as an explicit arg so it stays a deliberate act, not a blanket--yes).-y's help: it does not satisfy strong approval. Ideallyob deploy -yon a strong plan fails fast stating that (it already points to approve+approval, which is good).obintojust/CI hit this immediately: ajust deploythat runsob deploy --plan …always fails on strong plans because there's no scriptable approve step to put in front of it.2. A no-op migration downgrades rollback safety to
changed=unknownThis deploy touched no schema, and the plan correctly showed
job:migrate … changed=false. But at execution:The gate mounts a result file and reads it back (
internal/engine/gate.go:112-165); when the job doesn't writeOB_RESULT_FILE, it falls toreason = "result file is missing"→changed=unknown(internal/engine/gate.go:171,202). The stock atlas migrate image doesn't write that file on a clean no-op, so a zero-change deploy silently loses automatic rollback.Suggested: treat a clean migrate exit with no diff as
changed=false(keep rollback open), or have the bundled migrate wrapper always write a result on success. A no-op migration shouldn't be scarier than a real one.3. Tight, shared plan/approval expiry
The bound plan expired ~15 min after generation (
expires: 2026-07-14T21:34:51Z), and plan → approve → deploy all share that window. For a flow whose whole point is a human approval pause, 15 min is short. A longer default (or a visible countdown / "regenerate" hint on expiry) would cut down on plan-regeneration churn.4. Artifact hygiene
Plan and approval artifacts land in CWD (
ob approvedefaults--out ob-approval.json,cmd/ob/commands.go:186). Over a few releases the repo root accumulatesob-plan-*.jsonand nowob-approval-*.json(the grant is correctly0600). Consider defaulting these under an XDG/state dir, plus anob prunefor spent plans and consumed grants.5. Minor: dirty build in a release tool
The runner self-reports
ob 0.0.1-m0 (…+dirty). A tool deploying production from a dirty build is a smell — consider warning (or refusing without--force).Happy to send a PR for #1 (non-interactive
ob approve --confirm) if that direction sounds right.