Skip to content

fix(lifecycle): an OPEN PR's checkout needs its owner's consent, not lsof's silence - #1683

Merged
njrini99-code merged 2 commits into
mainfrom
agent/lifecycle-pr-ownership
Aug 30, 2026
Merged

fix(lifecycle): an OPEN PR's checkout needs its owner's consent, not lsof's silence#1683
njrini99-code merged 2 commits into
mainfrom
agent/lifecycle-pr-ownership

Conversation

@njrini99-code

Copy link
Copy Markdown
Owner

Reproduced invariant violation, fixed at the classifier.

On 2026-08-30 npm run worktrees:retire removed a concurrent session's
checkout (agent/round-type-reclassify, PR #1681, OPEN). Every mechanical
signal said disposable: clean, tip identical to its pushed remote, no process
whose cwd lsof could see. Nothing was lost — parking keeps the branch — but
the checkout had an owner and the tool could not tell.

The unsound step is reading silence as absence:

hasLiveProcess === true    proof of activity        — a sound veto
hasLiveProcess === false   NOT proof of inactivity  — an agent session between
                           two tool calls has no visible cwd

classifyWorktree's own header stated the defect as design ("parking does NOT
consult the PR at all"). It is rewritten in the same commit.

What changed

Not covered, and registered

A worktree whose branch has no PR is still parked on reproducibility alone.
Requiring recorded intent for every branch would make --park unable to act at
all. Registered as WORKTREE_PARK_NO_PR_OWNERSHIP.

Verified

preflight 0 · 1269 files / 12,101 tests 0 · docs:check 0 · knowledge:check 0 ·
markdown ratchet 30515 unchanged · control-plane:verify VERIFIED exit 0
(was CONTROL FAILURE) · lifecycle suite 60 passed.

Failure injection: disabling the ownership gate turns 8 tests red, including
the end-to-end CLI case where --park refuses a fixture worktree whose PR is
OPEN with no disposition. Lib restored from a hashed backup, verified identical.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NbDxyygyXRUEERuGocpZZH

…lsof's silence

Reproduced 2026-08-30: `npm run worktrees:retire` removed a concurrent
session's checkout (agent/round-type-reclassify, PR #1681, OPEN). Every
mechanical signal said disposable — clean, tip identical to its pushed
remote, and no process whose cwd `lsof` could see. Nothing was lost, because
parking is defined to keep the branch and PARKABLE already required the tip
to match its remote. But the checkout had an owner and the tool could not
tell, so this is a classifier defect, not a slip.

The unsound step is reading silence as absence. `lsof +D` samples one
instant:

    hasLiveProcess === true    proof of activity        — a sound veto
    hasLiveProcess === false   NOT proof of inactivity  — an agent session
                               between two tool calls has no visible cwd

classifyWorktree's own header stated the defect as design — "parking does
NOT consult the PR at all" — so the comment is rewritten in the same commit.
A file whose prose contradicts its code is the failure this program exists
to remove.

WHAT CHANGED

New worktree verdict KEEP_PR_OWNER_INTENT_REQUIRED, deliberately neither
ACTIVE (nothing proved anyone is using it) nor UNKNOWN (the PR read fine;
what is missing is a decision). It is in REQUIRES_HUMAN_VERDICTS, so the
standing authorization already excludes it.

config/open-pr-dispositions.json gains `worktree_policy`, because the
disposition LABEL was being asked to imply an ACTION readers had to infer:

    KEEP                  never parked automatically
    PARK_IF_REPRODUCIBLE  park once clean and pushed; the branch stays

A missing row, an unrecognised policy, or a disposition of ACTIVE/UNKNOWN
all KEEP. #1659 — an open PR waiting on a physical-device test — is released
explicitly by its owner and stays parkable, which is the case the park/retire
split exists for.

The registry is now current state in BOTH directions. It carried ACTIVE rows
for #1623, #1638, #1679 and #1680 after they closed or merged; control-plane
-verify now fails on a stale row as well as a missing one, and on a
worktree_policy outside the vocabulary. That vocabulary check lives in the
verifier because the lifecycle tool must fail SAFE on a malformed entry
(unrecognised policy => KEEP) and would therefore never report one.

#1681's row is recorded ACTIVE/KEEP, transcribed from observable state — PR
OPEN, worktree checked out — by this session and not by its owner, who
should confirm it. Recorded rather than left blank because leaving it blank
is exactly what let --retire park it.

WHAT THIS DOES NOT COVER

A worktree whose branch has NO PR is still parked on reproducibility alone —
the same defect class, with no PR to key a disposition on. Requiring recorded
intent for every branch would make --park unable to act at all, which defeats
the reason parking exists. Registered as WORKTREE_PARK_NO_PR_OWNERSHIP rather
than quietly accepted.

VERIFIED

preflight 0 · 1269 files / 12,101 tests 0 · docs:check 0 · knowledge:check 0
· markdown ratchet 30515, unchanged · control-plane:verify VERIFIED exit 0
(was CONTROL FAILURE on open-pr-residue) · lifecycle suite 60 passed.

Failure injection: disabling the ownership gate turns 8 of them red,
including the end-to-end CLI case where --park refuses a fixture worktree
whose PR is OPEN with no disposition. The lib was restored from a hashed
backup and re-verified byte-identical.

Assumption stated once: this ran in the canonical checkout rather than a
worktree. HELM_MAX_MUTATION_WORKTREES=1 and the other session holds the only
mutation workspace, so new-worktree.sh refuses by design; AGENTS.md permits a
single session in canonical, and the concurrency hazard the worktree rule
addresses does not apply because the other session is isolated in its own.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NbDxyygyXRUEERuGocpZZH
@qodo-code-review

Copy link
Copy Markdown

ⓘ Qodo reviews are paused because your trial has ended. Ask your workspace admin to add credits to resume reviews. Manage billing

@supabase

supabase Bot commented Aug 30, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project qmnssrrolpinvwjjnufo because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@vercel

vercel Bot commented Aug 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
helmv3 Ignored Ignored Preview Aug 30, 2026 3:14pm

Request Review

@coderabbitai

coderabbitai Bot commented Aug 30, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e68fadd2-12bd-4251-88ab-ca3fdbe74323

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…cost

Opening #1683 made control-plane:verify red on open-pr-residue immediately,
because the key set must now equal the live open-PR set exactly. That is the
chosen tradeoff, not a surprise: a PR number does not exist until the PR does,
so the row can only ever land in a follow-up commit.

KEEP rather than PARK_IF_REPRODUCIBLE: this work runs in the canonical
checkout, which classifyWorktree holds ACTIVE before consulting anything else.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NbDxyygyXRUEERuGocpZZH
@njrini99-code
njrini99-code merged commit 201b2a3 into main Aug 30, 2026
48 checks passed
@njrini99-code
njrini99-code deleted the agent/lifecycle-pr-ownership branch August 30, 2026 15:25
njrini99-code added a commit that referenced this pull request Aug 30, 2026
…said otherwise (#1684)

* docs(authority): one owner per kind of truth, and six documents that said otherwise

Helm already has every primitive a knowledge system needs. What it did not have
was a statement of which one owns which kind of truth — and six current-state
documents were quietly asserting things that had stopped being true. All six
were verified against the live repository before being touched.

THE ADR

memory/decisions/ADR-2026-08-30-helm-knowledge-authority.md records the
hierarchy, the projection rule, identity normalization, the three-feature-map
split, and the alternatives rejected (a fourth registry, merging the two
feature maps, a KNOWN_ISSUES.md, deleting the superseded documents, a prose
contradiction detector). The decisions directory's own README names this exact
reconciliation as the case it exists for.

The load-bearing rule: a projection may summarise an authority; it may never
become a second copy with its own update path.

WHAT WAS FALSE, AND VERIFIED FALSE

memory/system/golfhelm-engineering-os.md — the runtime contract every session
loads — claimed a guard-feature-context PreToolUse hook DENIES governed edits
without loaded context. There is no such file on disk and no such hook in
.claude/settings.json; the only wired PreToolUse hook matches Write|Edit|
MultiEdit and refuses canonical writes. It also credited a guard-bash.sh rule
deleted 2026-08-27. Corrected to POST-HOC DETECTION, which is what
docs/CONTROL_PLANE_ENFORCEMENT.md has recorded the whole time.

docs/README.md opened with "Everything in docs/ is hand-written prose. None of
it is generated" while two generated files sat in that directory. Its Start
Here list routed readers to an issue ledger whose own header reads
STATUS: SUPERSEDED, and has since 2026-07-10.

memory/ledgers/README.md documented a ledgers/operations/ directory that has
never existed — no directory, no file, no writer, no reference. Removed rather
than created: incident aftermath already has a per-feature home, and a fifth
slot with no distinct lifecycle is a place for entries to go unread.

memory/operations/release-queue.yml said it was "empty until then, not a
placeholder for hand-written entries" directly above five hand-written repair
units. The header now describes how it is actually populated.

memory/README.md said collapsing the two feature-doc generations was "work
still owed" and that CLAUDE.md routes to the losing one — CLAUDE.md no longer
does. Reframed; the actual collapse is the next PR.

docs/OBSERVABILITY.md implied every server failure reaches both Sentry and
admin_events. Its scope is narrowed to EMISSION and it now points at
docs/OBSERVABILITY_AUTHORITY.md for what each surface knows.

.claude/rules/shipping.md answered "worktree or canonical?" two different ways
in one file while AGENTS.md carried the real rule. AGENTS.md owns workspace
policy; this file links.

Three prose counts removed under shipping.md §1 — "there are 22", "the other
44", "192 commits stale". An index that breaks that rule about itself is not
one to trust about anything else.

A GATE THAT COULD NOT SEE A NAMESPACE

Writing the ADR turned docs:schema-drift red on `golf_round_lifecycle` — a
memory/registry.yml FEATURE ID, not a table. The gate greps memory/** for
golf_*/baseball_* tokens, and feature ids share that shape, so the first .md
under memory/ to name one in prose failed as a phantom table. Same
substring-is-not-a-mechanism error this repo has now made three times.

Fixed by excluding DECLARED registry keys only — parsed from the registry, not
pattern-matched — so a misspelled table is still caught, and printed on every
run so the exemption is never silent. Five tests; injection (forcing the
exemption set empty) turns two of them red.

docs/HELM_OS.md lands here rather than later because the ADR and the docs index
both need to point somewhere, and a dangling forward reference is what
docs:path-drift exists to refuse.

VERIFIED

preflight 0 · 1270 files / 12,106 tests 0 · docs:check 0 · knowledge:check 0 ·
markdown ratchet 30515, unchanged · control-plane:verify VERIFIED exit 0.

Also removes #1683's disposition row, now stale — the closing half of the cost
the ADR states: every PR turns the verifier red twice, once on open and once on
merge, because the registry must equal the live open-PR set exactly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NbDxyygyXRUEERuGocpZZH

* chore(dispositions): record #1684

The open-half of the cost the ADR states. Row lands after the PR exists
because there is no other order in which it can.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NbDxyygyXRUEERuGocpZZH

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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