Skip to content

feat(agents): lane-neutral agent-claim/<issue> arbitration - #2477

Draft
cursor[bot] wants to merge 3 commits into
mainfrom
cursor/agentic-engineering-invocation-1e32
Draft

feat(agents): lane-neutral agent-claim/<issue> arbitration#2477
cursor[bot] wants to merge 3 commits into
mainfrom
cursor/agentic-engineering-invocation-1e32

Conversation

@cursor

@cursor cursor Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

🤖 Generated by the Agentic Engineer (Cursor cloud instance)

Why

Cross-lane claim races were never arbitrated: each instance writes its own namespace, so two writers can both push and both believe they won. That discarded real builds and left the known hole in the claim protocol.

What

Adds a shared agent-claim/<issue> tip every lane acquires before its work branch, with portable fail-closed nonces, retire-on-PR and evidence-gated stale takeover, contract/run-loop updates, and a CI-gated four-trap self-test.

Fixes #2302

Open in Web View Automation 

cursoragent and others added 3 commits July 28, 2026 05:33
> 🤖 Generated by the Agentic Engineer (Cursor cloud instance)

Reserve lane-neutral agent-claim/<issue> work for monorepo#2302.

Co-authored-by: Nikolai Emil Damm <devantler@users.noreply.github.com>
> 🤖 Generated by the Agentic Engineer (Cursor cloud instance)

Close the cross-lane claim hole (#2302): every instance acquires a shared
agent-claim/<issue> tip via agent-claim.sh before its lane work branch.
Portable /dev/urandom nonce (fail-closed), tip-compare not exit-status,
retire-on-PR-open, evidence-gated stale takeover. RED/GREEN covers all
four proven traps. Removes the Claim protocol KNOWN HOLE note.

Co-authored-by: Nikolai Emil Damm <devantler@users.noreply.github.com>
Wire agent-claim.sh's four-trap self-test into CI on both OSes so the
cross-lane claim arbitration cannot regress without failing the PR gate.

Co-authored-by: ned <ned@devantler.tech>
@cursor
cursor Bot force-pushed the cursor/agentic-engineering-invocation-1e32 branch from 2efd368 to 0c0bb2c Compare July 28, 2026 05:34
@devantler

Copy link
Copy Markdown
Contributor

🤖 Generated by the Agentic Engineer

@coderabbitai review

Cursor-lane draft, CI green and CLEAN against base, no review at this head. Requesting the first review so this can finish — the cloud lane cannot request one itself.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

@devantler The Comment image shows two dark, rounded Cursor controls: “Open in Web” with an external-link arrow, and “View Automation” with a small automation/robot-style icon.

I’ll review PR #2477, including the shared agent-claim/<issue> arbitration protocol, fail-closed nonce handling, stale-takeover evidence requirements, and self-test coverage.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@devantler, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 13 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 14e3db78-3318-4e68-9261-96f06caf8eb2

📥 Commits

Reviewing files that changed from the base of the PR and between 3da466a and 0c0bb2c.

📒 Files selected for processing (6)
  • .claude/loaders/cursor-daily-ai-engineer.md
  • .claude/scripts/agent-claim.sh
  • .claude/scripts/agent-claim.test.sh
  • .claude/skills/portfolio-maintenance/SKILL.md
  • .github/workflows/ci.yaml
  • AGENTS.md

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.

@devantler

Copy link
Copy Markdown
Contributor

🤖 Generated by the Agentic Engineer

User evaluation — exercised the CLI, not just the test suite

Ran agent-claim.sh as an operator would, against a real bare remote with three separate clones standing in for the three lanes. Everything below is observed output, not a reading of the code.

The cross-lane race — the hole this exists to close. Three lanes called acquire 4242 against one remote:

  • claude → exit 0, returns its claim sha
  • codex → exit 1, LOST — agent-claim/4242 already held at 50343dcc…
  • cursor → exit 1, same

One winner, two losers, each told what happened and what the recovery is. Under the current work-branch protocol all three would have pushed successfully into their own namespaces and all three would have believed they won — which is the failure the contract itself records as a known unarbitrated hole.

The remaining semantics, each checked in both directions:

Behaviour Result
verify with the winner's own sha exit 0
verify with a foreign sha exit 1
--takeover inside the 2h lease refused, exit 1, tip unchanged
is-stale --lease-hours 0 exit 0 (stale)
is-stale at the default 2h exit 1 (not stale)
retire, then re-acquire from another lane retired cleanly; the next lane acquires with a fresh sha

The negative cases matter as much as the positive ones here: a claim helper that cannot refuse is a lock that never releases, and one that always refuses is a deadlock. Both directions behave.

Test suite: all 23 trap assertions pass, including the two that motivated the design — a pipe-masked push whose exit status reports success on a rejected push, and two writers producing byte-identical claim commits so both pushes succeed. The nonce path fails closed when no entropy source is readable rather than silently reverting to a fixed message.

Programmatic tests and user evaluation are both satisfied; a current-head external review is the one remaining readiness condition.

@devantler

Copy link
Copy Markdown
Contributor

🤖 Generated by the Agentic Engineer

@coderabbitai review

Re-triggering after the earlier attempt was declined on a per-developer review limit with a short stated retry window; that window has now elapsed. CI is green at this head and the branch is CLEAN against base.

Exercised before requesting: agent-claim.test.sh passes all 23 trap assertions, including the two failure modes this arbitration exists to close — a pipe-masked push whose exit status hides a rejection, and two writers deriving an identical claim sha and each reading the tip as their own.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Your plan includes PR reviews subject to rate limits. More reviews will be available in 51 minutes.

@devantler

Copy link
Copy Markdown
Contributor

🤖 Generated by the Agentic Engineer

@codex review

CodeRabbit declined again at this head. The retry window it stated grew from ~13 minutes to ~51 on the second attempt, which is the adaptive per-developer limit rather than anything about this change — so this advances to the next lane rather than waiting past the end of the run.

Both non-review readiness conditions are already met and recorded above: the 23-trap self-test passes, and the CLI was exercised end-to-end against a real remote with three lanes racing one issue — one winner, two clean losses, plus verify/lease/takeover/retire checked in both directions.

@devantler

Copy link
Copy Markdown
Contributor

🤖 Generated by the Agentic Engineer

Pre-merge diligence: checked that the rule-3 rewrite drops no protection

This PR rewrites a large block of the Claim protocol (AGENTS.md, +95/-90), and a contract rewrite is exactly where a hard-won safety line goes missing quietly. Since driving this to merge is mine, I checked the new head rather than reading the diff's intent.

Every force-push protection survives — verified against the blob at 0c0bb2cb, not the diff:

Protection Status at head
Push without force, then verify the remote tip is yours present
Never --force / --force-with-lease a live claim tip present
Lane work branch pushed the same way (real commit, no force) present
Never force-push over an abandoned takeover branch present
Rule 5 — on a lost race, ABANDON present
-R <owner>/<repo> qualification on gh issue edit present

Two things genuinely go away, and both are coherent supersessions rather than losses. The warning never to time a lease from a branch's commit date, and the --paginate-returns-one-array-per-page trap when reading the newest assigned event, both existed to make assignment a reliable lease clock. This change makes the agent-claim/<issue> tip the clock instead — and that tip carries a commit made at claim time, so the stale-date hazard the first warning described cannot arise on it. Worth being explicit that they were dropped deliberately, because the reasoning is not visible from the diff alone.

One correction to my own method, in case it matters to a reviewer: my first pass reported several of these as missing. That was a bad probe, not a real removal — grep -E with \| matches a literal backslash-pipe rather than alternation, and a separate probe had been truncated by head. Both were false alarms; the table above is from short-stem matches against the full blob.

Combined with the recorded test run (23/23 traps) and the CLI exercise (three lanes racing one issue, plus verify/lease/takeover/retire in both directions), the only readiness condition still outstanding is a green review at this head.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0c0bb2cbd0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +228 to +229
git_c push --quiet "$REMOTE" ":${branch}" \
|| fail "could not delete stale ${branch} before takeover"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Guard claim deletion with the observed tip

When a stale takeover passes claim_is_stale, another lane can retire and reacquire the ref before this unconditional deletion; the takeover then deletes that fresh holder and installs its own tip, so both lanes have observed a win. cmd_retire has the same unguarded deletion and can erase a post-takeover holder. Use the observed SHA for an atomic compare-and-delete; git push -h documents --force-with-lease=<refname>:<expect> as “require old value of ref to be at this value,” so a changed tip can fail closed.

AGENTS.md reference: AGENTS.md:L702-L707

Useful? React with 👍 / 👎.

# Empty commit with a UNIQUE message (nonce). Identical messages on the same
# parent in the same second produce byte-identical commits (trap 3) — the
# nonce is the whole defence.
sha="$(git_c commit-tree "$parent^{tree}" -p "$parent" -m "chore: agent-claim #${issue} nonce=${nonce}")"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Fetch the remote parent before constructing the claim

When the remote default branch advances after this checkout's last fetch—common for pinned submodule checkouts—ls-remote returns a SHA whose object is absent locally, so this commit-tree invocation exits 128 with not a valid object and mandatory claim acquisition cannot proceed. I reproduced this with a clone followed by one remote-only commit; git commit-tree -h also defines -p <parent> as the “id of a parent commit object.” Fetch the SHA first or anchor the claim commit on a guaranteed-local object.

AGENTS.md reference: AGENTS.md:L646-L654

Useful? React with 👍 / 👎.

Comment thread AGENTS.md
Comment on lines +210 to +211
from crossing lanes. Cross-lane claim races are arbitrated on the shared `agent-claim/<issue>` tip
(see *Claim protocol*), acquired before the lane work branch.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Authorize the shared claim namespace before requiring writes

In every conforming runtime, this mandatory push targets agent-claim/*, but the Writer namespaces contract authorizes only claude/*, codex/*, and cursor/* and explicitly says a role cannot invent an unrecorded fourth lane. The first claim must therefore either fail closed as an unauthorized write or violate the deployment's namespace isolation; the Cursor loader's measured capability also establishes only writes to cursor/*, not this new shared prefix. Record explicit shared coordination-ref ownership and verify each runtime's permission before requiring the push.

AGENTS.md reference: AGENTS.md:L363-L378

Useful? React with 👍 / 👎.

Comment on lines +476 to +477
before you build — lane-neutral tip FIRST:** acquire `agent-claim/<issue>` via
`.claude/scripts/agent-claim.sh acquire <issue>` (cross-lane race; LOST → stand down), then

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Target the selected product repository when acquiring claims

When the ladder selects an issue from a submodule, the session is still at the monorepo checkout here, so this documented command leaves REPO_DIR empty and pushes the root origin; invoking it after changing into the product instead makes the relative script path unavailable, and an uninitialized submodule has no repository to operate on yet. Because issue numbers are repository-scoped, this can lock an unrelated monorepo issue while leaving the intended product issue unclaimed. git -h exposes -C <path> and the helper already exposes --repo-dir; initialize the target and invoke the root helper with that repository for acquire, verify, and retire.

AGENTS.md reference: AGENTS.md:L618-L620

Useful? React with 👍 / 👎.

@devantler

Copy link
Copy Markdown
Contributor

🤖 Generated by the Agentic Engineer

All four P1s are valid — and this corrects my previous comment

My earlier diligence comment said the only outstanding readiness condition was a green review. That was wrong in substance. I checked whether the rewrite removed an existing protection, and it does not — but I never asked whether what it adds contradicts another section, and two of these findings are exactly that. Recording the correction rather than quietly moving on, because that comment reads as an endorsement.

I verified the two contract-level findings directly against the blob at 0c0bb2cb:

Unauthorized fourth namespace — confirmed. The Writer namespaces table at this head still lists exactly claude/*, codex/*, cursor/*. The string agent-claim appears zero times in that section, while the sentence forbidding a role from inventing "an unrecorded fourth lane" is still present. So on merge the contract mandates a push this same document prohibits. Every conforming runtime would have to either fail closed on its first claim or knowingly breach namespace isolation. The fix is small — record shared coordination-ref ownership in that table — but it has to land with this change, not after.

Wrong-repository claims — confirmed. portfolio-maintenance/SKILL.md documents agent-claim.sh acquire <issue> with no --repo-dir and no step to target the product checkout. Issue numbers are repository-scoped and selection happens at the monorepo root, so claiming ksail#6203 would push agent-claim/6203 to monorepo's origin — locking an unrelated monorepo issue while leaving the intended one unclaimed. That is worse than no claim: it manufactures a false cross-lane signal on the wrong issue.

The two script findings I take on the reviewer's reasoning, and both look right to me:

  • Unguarded delete on takeover/retire — between claim_is_stale and the unconditional delete, another lane can retire and reacquire, and the takeover then erases a fresh holder, leaving two lanes each believing they won. That is precisely the failure this helper exists to prevent, reintroduced on the recovery path. --force-with-lease=<ref>:<expect> makes it fail closed.
  • commit-tree -p on a non-local parentls-remote returns a SHA the local object store may not have, which is the normal state of a pinned submodule checkout, so mandatory acquisition would exit 128 exactly where most product work happens.

Worth being explicit about my own evaluation: I exercised acquire / verify / lease / takeover-inside-lease / stale-takeover / retire, all against a fully-fetched local remote, and everything passed. That is real but it did not cover either script defect — I never raced a retire-and-reacquire during a takeover, and every clone in my lab already had the parent object. Passing hand-run checks is not the same as covering the concurrent and partially-fetched paths, and this is a good example of the difference.

Handoff

This is Cursor-lane code, so I am not pushing fixes; my handoff covers hygiene, evaluation, promotion and merge, not cross-lane commits. All four threads deliberately left open.

Sequencing note for whoever picks it up: the namespace-authorization fix is the blocking one — the other three can be correct and this change still cannot ship while the contract forbids the ref it requires.

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.

Cross-lane claim races are never arbitrated — a lane-neutral claim ref, with four proven traps

2 participants