Skip to content

fix: harden destructive ops — root-level prune/sync guards, ancestry session check, durable branch prefix - #26

Merged
zheng-weihao merged 5 commits into
mainfrom
prune-protection
Aug 3, 2026
Merged

zheng-weihao merged 5 commits into
mainfrom
prune-protection

Conversation

@zheng-weihao

Copy link
Copy Markdown
Member

Why

An agent ran orbit prune from inside a workspace: it deleted a sibling workspace, read the self-protection skip message (you are currently in this workspace) as instructions, cd'd out, and pruned the workspace it was working in. The protection checked state the agent controls (shell CWD) and its refusal message doubled as a bypass guide. git worktree remove --force also destroyed uncommitted changes with no check at all.

What

Prune — four guards, all on state an agent cannot change by cd-ing (spec: docs/spec-lifecycle.md → Prune Safety Guards)

  • Runs only from the project root — refuses inside any workspace (cross-scope destructive ops belong to the scope above)
  • Skips a workspace the session is rooted in, detected by walking the process ancestry (/proc on Linux, lsof on macOS; physical-path normalization so symlinked roots still match) — best-effort, announces its blind spot, orbit doctor reports the facility
  • Skips candidates with uncommitted changes, non-pool git repos (objects live nowhere else), or unmerged jots — all reasons reported together; --force overrides data guards only, never the scope guards
  • Refusal messages name no bypass — new refusal/skip class registered in docs/spec-warnings.md
  • Removed from the skill's action surface — the skill's only reader is the one role prune refuses to serve; documenting the guards to that reader was handing over a bypass map. Enforcement replaces documentation (skills/CONSTRAINTS.md → "Prune is out of scope for the skill")

sync — --force / --branch are root-level only (they destroy/re-point shared pool state); --branch retires only the replaced branch's fetch refspec and rolls back on failure. Bare sync unaffected.

branch prefix — moved from ORBIT_BRANCH_PREFIX env to orbit config branch.prefix (default ws): the value names every branch orbit creates and selects which branches prune deletes, so it must be durable. Validated on write; refused while branches still carry the current prefix. BREAKING.

Repo names validated as pool basenames across sync/add/info/memo/clone --name — a traversing name (sync ../<ws>/<repo> --force) previously aimed git reset --hard at a worktree's checked-out branch. Clone basename outside the contract points at --name.

jot queue stored as [jot "<repo>"] subsections — names plain git-config keys can't hold (my_repo, 2048) now round-trip.

auto-approve hooks match destructive flags on normalized tokens — '--force', --force'', \-\-force no longer slip past (both hooks/auto-approve.sh and the OpenCode plugin); sync --branch joins the prompt tier.

inference — workspace/repo inference compares physical paths (commands work through symlinked cwds, e.g. /tmp → /private/tmp).

CHANGELOG rewritten CNCF-style (Urgent Upgrade Notes + Changes by Kind, PR-attributed), complete for v0.1.0..HEAD — release-ready: flipping Unreleased → 0.2.0 (+ ORBIT_VERSION bump + filling this PR's number in the Security entries) is all the release needs.

Test plan

  • CI bats suite (27 files, incl. new tests/24_auto_approve.bats; 09_prune +20 cases covering root-only/session/data guards, 14_config branch.prefix suite, 18_sync root-guard + refspec retention, 20_jot subsection round-trip)
  • OpenCode plugin tests (node --test .opencode-plugin/plugin.test.ts — 16/16 locally)
  • Local spot-checks: 263/263 bats + 16/16 plugin passed pre-PR; full suite re-run delegated to CI

…session check, durable branch prefix

prune: run from project root only; skip workspaces the session is rooted
in (process-ancestry check, not CWD); skip dirty/foreign-repo/unmerged-jot
candidates without --force; refusal messages name no bypass; removed from
the skill's action surface (enforcement replaces documentation). Branch
cleanup now names squash/rebase-merged branches in the skip line (content
equivalence via git merge-tree, git cherry fallback) with the exact
native-git cleanup command for the human operator.

sync: --force/--branch are root-level only; --branch retires only the
replaced branch's fetch refspec and rolls back on failure.

branch prefix: moved from ORBIT_BRANCH_PREFIX env to project config
(branch.prefix) — validated on write, immovable while branches carry it.

repo names validated as pool basenames across sync/add/info/memo/clone
(path-traversal fix); clone basename outside the contract points at --name.

jot queue stored as [jot "<repo>"] subsections — names plain git-config
keys can't hold (my_repo, 2048) now round-trip.

auto-approve hooks match destructive flags on normalized tokens (quotes/
backslashes stripped); sync --branch joins the prompt tier.

workspace/repo inference compares physical paths (symlinked cwd); session
guard announces its blind spot when ancestry is unreadable; doctor reports
the ancestry facility.

CHANGELOG rewritten CNCF-style (Urgent Upgrade Notes + Changes by Kind,
PR-attributed) covering v0.1.0..HEAD, release-ready for 0.2.0.

Signed-off-by: zheng-weihao <zheng-weihao@outlook.com>
…te the cleanup command

git cherry failing (unresolvable upstream, or any error) left an empty
'+' set that the negated grep inverted into 'content upstream' — handing
out branch -D for content that was never verified. Capture cherry's
output and fail closed; match via here-string (no producer pipe under
pipefail).

Also quote the path and branch in the printed cleanup command so it
survives copy-paste from paths with spaces; message contract and registry
updated, plus a test pinning the unresolvable-upstream case.

Signed-off-by: zheng-weihao <zheng-weihao@outlook.com>
…ndent ancestry refusal, structural workspace detection, blind walk withholds cd replay

Signed-off-by: zheng-weihao <zheng-weihao@outlook.com>
…ng exit defeats Linux exec-collapse that made assertions platform-dependent

Signed-off-by: zheng-weihao <zheng-weihao@outlook.com>
…ame exec-collapse fix as prune

Signed-off-by: zheng-weihao <zheng-weihao@outlook.com>
@zheng-weihao
zheng-weihao merged commit f54573d into main Aug 3, 2026
2 checks passed
@zheng-weihao
zheng-weihao deleted the prune-protection branch August 7, 2026 09:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant