Skip to content

fix(repo): hold a checkout across an approval park β€” supervised write-tier deadlock (#796) - #803

Merged
oxoxDev merged 4 commits into
tinyhumansai:mainfrom
YellowSnnowmann:fix/796-checkout-survives-park
Aug 13, 2026
Merged

fix(repo): hold a checkout across an approval park β€” supervised write-tier deadlock (#796)#803
oxoxDev merged 4 commits into
tinyhumansai:mainfrom
YellowSnnowmann:fix/796-checkout-survives-park

Conversation

@YellowSnnowmann

@YellowSnnowmann YellowSnnowmann commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes the supervised-mode deadlock the write tier (#735/#736) hits in real use (issue #796): an agent can check out a repo, edit, and commit, but never publish β€” the working tree is deleted between every parked step, so the commit repo_publish needs is gone by the time it runs.

Root cause. Under supervised every step of repo_checkout β†’ edit β†’ git_operations commit β†’ repo_publish is Reach::Consequence and parks, and each park ends the turn. The CheckoutJanitor deletes the checkout at every turn end (and again at the next turn's claim), so the tree never survives from one parked step to the next. The tool contract β€” "deleted at task end" (#245 Β§5, #247 Β§7) β€” was never actually honoured; the janitor was per-turn.

Fix. A checkout a task turn parks with is held on a task-keyed retained set the janitor does not touch, and deleted only when the task truly ends.

  • CheckoutLedger gains retain_for_task / reclaim / purge_task / sweep_orphans (+ has_active) beside the existing turn-scoped list.
  • The parked call's task is carried on the grant (GrantedCall::origin_task, StandingGrant::origin_task), stamped at mint from the approval's approval_task join.
  • The approval re-issue stamps the resumed task on the ledger (so repo_publish can name its branch β€” which feat(repo): host-side repo_publish β€” namespaced branches, structural push refusals, approval-gatedΒ #735 could not do on this path) and reclaims the checkout the parked step left, so the resumed commit and publish operate on the same tree. If it parks again, the tree is held again.
  • Every janitor claim sweeps any task whose approval was denied or expired (no live grant names it) β€” the deny/expire cleanup, done lazily in the harness with no runtime coupling.
  • repo_checkout reuses a reclaimed tree instead of re-cloning over the agent's own commits.

Autonomous/full mode is unaffected β€” the whole chain runs in one un-parked turn there, so the retention never engages.

Stacking

Built on #778 (feat/735-repo-publish), which is not yet on main, so like #784 this PR's diff shows the parent's changes too until #735 merges. The net #796 change is the two commits 0c39c054 (ledger primitive + tests) and 103aed2d (origin-task wiring + re-issue reclaim + docs). Rebase onto main follows #735 merging.

Test plan

  • cargo fmt --all -- --check
  • cargo clippy --all-targets -- -D warnings (default)
  • cargo clippy --features openhuman,tinycortex --all-targets -- -D warnings
  • New ledger tests: retainβ†’purge-survivesβ†’reclaim, purge_task, sweep_orphans (live vs orphaned), retain-twice
  • New brain tests: an_approved_grant_reclaims_the_task_checkout_it_resumes, an_orphaned_task_checkout_is_swept_at_the_next_janitor_claim
  • Touched modules regression: runtime::grants, runtime::cycle, harness::repo, harness::brain β€” 204 passed, 0 failed (RUST_MIN_STACK=16777216, as CI sets)

Closes #796.

πŸ€– Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added approval-gated repository publishing for eligible, push-capable repositories.
    • Publishes exact approved commits through a staged workflow without directly pushing.
    • Repository checkouts now persist across supervised approval pauses, allowing work to continue in the same tree.
  • Bug Fixes

    • Improved cleanup of completed, denied, expired, and orphaned checkouts.
    • Added safeguards for read-only repositories, unsafe task identifiers, unauthorized branches, and non-fast-forward updates.
    • Preserved task context across approvals and redispatched work.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

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

Next review available in: 22 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: da1b1d49-ce2b-4229-b98c-ab0e5ac7e979

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 891a7ba and 500b0eb.

πŸ“’ Files selected for processing (10)
  • docs/spec/runtime/repos.md
  • src/company/runtime.rs
  • src/harness/brain.rs
  • src/harness/repo.rs
  • src/harness/repo/test.rs
  • src/policy/consequence.rs
  • src/runtime/cycle.rs
  • src/runtime/grants.rs
  • src/runtime/journal.rs
  • src/server/operator.rs
πŸ“ Walkthrough

Walkthrough

The change adds task-scoped checkout retention across approval redispatches and introduces approval-gated repository publishing. It stages exact commits on host-managed refs, validates publish permissions, and pushes only approved commits.

Changes

Task-origin routing and checkout lifecycle

Layer / File(s) Summary
Task-origin grant routing
src/runtime/grants.rs, src/runtime/cycle.rs, src/harness/brain.rs, src/harness/policy.rs, src/runtime/journal.rs, src/policy/consequence.rs, src/server/operator.rs
Approval grants carry origin_task. Redispatches preserve this metadata, and live grants identify task checkouts that can remain.
Task-scoped checkout lifecycle
src/harness/repo.rs, src/harness/brain.rs, src/harness/repo/test.rs, docs/spec/runtime/repos.md
Checkouts can be retained, reclaimed, purged, and swept across approval pauses. Tests cover batched redispatches and orphan cleanup.

Repository publishing

Layer / File(s) Summary
Publish tool wiring and staging
src/harness/build.rs, src/harness/repo.rs, src/policy/consequence.rs, src/harness/repo/test.rs
The write tier exposes repo_publish only when grant, manager, binding, credential, and storage conditions pass. The tool stages committed checkout content and queues approval.
Approved publish execution
src/runtime/repo_manager.rs, src/runtime/cycle.rs, src/runtime/repo_manager/test.rs
The manager stages task-namespaced refs and pushes the exact approved commit. Validation rejects unsafe task IDs, unauthorized refs, and divergent updates.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers: oxoxdev

Poem

A rabbit keeps the checkout warm,
Through approval’s pause and storm.
Exact commits hop branch to branch,
Until approved to leave the ranch.
Old task trees fade when grants are gone.

πŸš₯ Pre-merge checks | βœ… 5
βœ… Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage βœ… Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check βœ… Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check βœ… Passed Check skipped because no linked issues were found for this pull request.
Description Check βœ… Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check βœ… Passed The title clearly identifies the checkout-retention fix for the supervised approval deadlock described in the changes.
✨ Finishing Touches πŸ’‘ 1
βš”οΈ Resolve merge conflicts πŸ’‘
  • Resolve merge conflict in branch fix/796-checkout-survives-park

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.

@coderabbitai coderabbitai 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.

Actionable comments posted: 6

🧹 Nitpick comments (6)
src/harness/repo/test.rs (1)

537-573: πŸ“ Maintainability & Code Quality | πŸ”΅ Trivial | ⚑ Quick win

Add a tool-level test for the new repo_checkout reuse branch.

has_active is covered here as a ledger method. The new early-return in RepoCheckoutTool::execute at src/harness/repo.rs Lines 845-869 has no test. That branch decides whether an agent's committed tree survives a resumed step, and it is the branch that drops the ref and pr arguments.

The fixtures needed already exist in this file: context, committed_checkout, and CheckoutLedger::record.

Cover three cases: reuse preserves the commit, the message names the reused commit, and a second call naming a different ref does not silently return the old tree.

As per coding guidelines for **/*.rs: "Add focused tests with every behavior change."

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/harness/repo/test.rs` around lines 537 - 573, The existing test only
covers CheckoutLedger::has_active; add focused tool-level coverage for
RepoCheckoutTool::execute’s repo_checkout reuse branch using the existing
context, committed_checkout, and CheckoutLedger::record fixtures. Verify reuse
preserves the committed tree, the returned message identifies the reused commit,
and a subsequent call with a different ref does not return the previously reused
tree.

Source: Coding guidelines

src/harness/repo.rs (1)

1207-1240: 🩺 Stability & Availability | πŸ”΅ Trivial

Plan the cleanup of oc/<company>/<task> mirror refs for a denied or expired publish.

execute stages the ref before the operator decides. A denied or expired approval never reaches perform_effect, so the staged ref stays in the mirror. Each published task then leaves one ref behind, and the mirror grows without bound over a long-lived company.

Two options: delete the ref on the deny and expiry paths, or sweep refs under refs/heads/oc/<company>/ whose task has no live grant, in the same style as sweep_orphans.

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/harness/repo.rs` around lines 1207 - 1240, Trace the REPO_PUBLISH_TOOL
approval lifecycle from execute through the deny and expiry handling, and remove
the staged oc/<company>/<task> mirror ref whenever the publish is not approved.
Implement cleanup on both denial and expiration paths, reusing the existing ref
identity and cleanup conventions such as sweep_orphans where appropriate, while
leaving approved publishes unchanged.
src/runtime/repo_manager.rs (2)

1211-1220: πŸ“ Maintainability & Code Quality | πŸ”΅ Trivial | ⚑ Quick win

Reuse first_line in fetch_into.

fetch_into inlines the same "first non-empty stderr line, else git wrote nothing to stderr" logic. Call the new helper there so one function owns that formatting.

♻️ Proposed change at lines 760-771
         if !out.ok {
             let refs = binding.branches.join(", ");
             return Err(OpenCompanyError::Store(format!(
-                "fetching {} ({refs}) failed: {}",
-                binding.url,
-                out.stderr
-                    .lines()
-                    .map(str::trim)
-                    .find(|l| !l.is_empty())
-                    .unwrap_or("git wrote nothing to stderr")
+                "fetching {} ({refs}) failed: {}",
+                binding.url,
+                first_line(&out.stderr)
             )));
         }
πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/runtime/repo_manager.rs` around lines 1211 - 1220, Update fetch_into to
call the existing first_line helper when formatting git stderr, removing its
duplicated first-nonempty-line fallback logic while preserving the current error
message behavior.

893-911: 🩺 Stability & Availability | πŸ”΅ Trivial | ⚑ Quick win

Pass --no-auto-gc on the staging fetch.

fetch_into passes --no-auto-gc and states the reason: checkouts alternate through the mirror's objects, so a prune must never run there. This new fetch writes into the same mirror but omits the flag. The mirror sets gc.auto=0 at init, so this is defence in depth rather than a live prune, but the invariant should hold on every fetch this module runs.

πŸ”§ Proposed fix
         let out = git::run(
             &mirror,
-            &["fetch", "--quiet", "--no-tags", &url, &refspec],
+            &["fetch", "--quiet", "--no-tags", "--no-auto-gc", &url, &refspec],
             None,
             None,
         )
πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/runtime/repo_manager.rs` around lines 893 - 911, Update the staging fetch
in the publish flow around the `git::run` call to include `--no-auto-gc`,
matching the existing `fetch_into` behavior. Preserve the current URL, refspec,
and error handling while ensuring every module-managed fetch protects the mirror
from automatic garbage collection.
src/harness/build.rs (1)

1914-1959: πŸ“ Maintainability & Code Quality | πŸ”΅ Trivial | ⚑ Quick win

Consider an exact-set assertion for the push-capable belt.

The neighbouring gates use exact-set comparisons (the_repo_grant_adds_exactly_two_tools, repo_write_grant_wires_no_tool_beyond_the_read_pair). This test uses contains only. An exact-set assertion would also fail if a later change widens the write belt beyond repo_publish.

♻️ Proposed addition
         let pushable = built_tool_names_with_repos_cap(&["repo.write"], 1, true);
         assert!(
             pushable.contains(&publish),
             "a push-capable `repo.write` must wire repo_publish: {pushable:?}"
         );
-        assert!(
-            pushable.contains(&"repo_checkout".to_string())
-                && pushable.contains(&"repo_pr".to_string()),
-            "the read pair must still be wired: {pushable:?}"
-        );
+        // Exact set: the read pair plus repo_publish, and nothing else.
+        let mut expected = built_tool_names(&[], false);
+        expected.push("repo_checkout".to_string());
+        expected.push("repo_pr".to_string());
+        expected.push(publish.clone());
+        expected.sort();
+        assert_eq!(
+            pushable, expected,
+            "a push-capable `repo.write` belt must be the read pair plus repo_publish"
+        );
πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/harness/build.rs` around lines 1914 - 1959, Update
repo_write_with_a_push_capable_credential_wires_repo_publish to assert the
complete expected tool set for the push-capable repo.write case, not merely
contains checks. Use the neighboring exact-set assertion pattern to require
repo_publish plus the existing read pair and fail if any additional tool is
wired; preserve the existing negative checks for read-only credentials, bare
repo, and plaintext backends.
src/runtime/repo_manager/test.rs (1)

1211-1229: πŸ“ Maintainability & Code Quality | πŸ”΅ Trivial | πŸ’€ Low value

Consider pinning the multi-segment case inside the namespace.

assert_publish_branch checks only the oc/<company>/ prefix and then validate_ref, which permits /. A branch such as oc/acme/a/b is therefore accepted, while stage_publish can never generate one. Add a row that records the intended answer, so a later tightening or loosening of assert_publish_branch is a deliberate change.

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/runtime/repo_manager/test.rs` around lines 1211 - 1229, Extend the
invalid-branch cases in the test covering push_published to include a
multi-segment branch such as oc/acme/a/b, asserting it returns
OpenCompanyError::InvalidRequest. Keep the existing valid-shaped commit ID and
error-message assertion unchanged so the test records the intended rejection
behavior in assert_publish_branch.
πŸ€– Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/harness/brain.rs`:
- Around line 399-419: Update the orphan-sweep flow around
CheckoutLedger::sweep_orphans so tasks with unresolved approvals are treated as
live and their retained checkouts are preserved until resolution. Use the
approval gate or journal’s pending-approval state in addition to
GrantSet::any_for_task, and add coverage that parks an approval, performs an
unrelated claim, and verifies the checkout remains.

In `@src/harness/build.rs`:
- Around line 1903-1908: Update the doc comment for the test containing
built_tool_names_with_repos and the write_granted assertion to remove the
obsolete claim that repo.write wires no tools or that repo_publish is pending;
state that a non-push-capable credential wires only the read pair.

In `@src/harness/repo.rs`:
- Around line 1193-1205: Update the Err branch handling stage_publish in the
repository publish flow to log the underlying error for diagnostics, while
returning a bounded, path-safe ToolResult error message to the agent instead of
interpolating err. Preserve the existing successful staging behavior and use the
file’s established clone-error messaging pattern.
- Around line 845-869: Update the reuse guard around has_active(&dest) to
compare the requested binding ref/pr with the reused checkout’s current ref
before returning success. If they differ, refuse the reuse and return an error
instead of proceeding with the earlier tree; otherwise include the tree’s
current ref and the requested ref in the success message while preserving reuse
for matching requests.

In `@src/runtime/repo_manager.rs`:
- Around line 1205-1209: Centralize the duplicate file_url helper by reusing the
existing implementation in harness/repo.rs and removing the runtime-local copy.
Ensure file_url receives an absolute path by validating or canonicalizing
caller-provided paths from agent_workspace and RuntimeBuilder before formatting
the URL, while preserving existing checkout behavior. Add a focused test
covering rejection or canonicalization of relative checkout paths.

In `@src/runtime/repo_manager/test.rs`:
- Around line 1091-1118: Update the checkout_with_commit helper’s git clone
invocation to explicitly select the main branch, ensuring the destination starts
from refs/heads/main rather than relying on the mirror’s default HEAD. Keep the
existing clone setup and subsequent commit behavior unchanged.

---

Nitpick comments:
In `@src/harness/build.rs`:
- Around line 1914-1959: Update
repo_write_with_a_push_capable_credential_wires_repo_publish to assert the
complete expected tool set for the push-capable repo.write case, not merely
contains checks. Use the neighboring exact-set assertion pattern to require
repo_publish plus the existing read pair and fail if any additional tool is
wired; preserve the existing negative checks for read-only credentials, bare
repo, and plaintext backends.

In `@src/harness/repo.rs`:
- Around line 1207-1240: Trace the REPO_PUBLISH_TOOL approval lifecycle from
execute through the deny and expiry handling, and remove the staged
oc/<company>/<task> mirror ref whenever the publish is not approved. Implement
cleanup on both denial and expiration paths, reusing the existing ref identity
and cleanup conventions such as sweep_orphans where appropriate, while leaving
approved publishes unchanged.

In `@src/harness/repo/test.rs`:
- Around line 537-573: The existing test only covers CheckoutLedger::has_active;
add focused tool-level coverage for RepoCheckoutTool::execute’s repo_checkout
reuse branch using the existing context, committed_checkout, and
CheckoutLedger::record fixtures. Verify reuse preserves the committed tree, the
returned message identifies the reused commit, and a subsequent call with a
different ref does not return the previously reused tree.

In `@src/runtime/repo_manager.rs`:
- Around line 1211-1220: Update fetch_into to call the existing first_line
helper when formatting git stderr, removing its duplicated first-nonempty-line
fallback logic while preserving the current error message behavior.
- Around line 893-911: Update the staging fetch in the publish flow around the
`git::run` call to include `--no-auto-gc`, matching the existing `fetch_into`
behavior. Preserve the current URL, refspec, and error handling while ensuring
every module-managed fetch protects the mirror from automatic garbage
collection.

In `@src/runtime/repo_manager/test.rs`:
- Around line 1211-1229: Extend the invalid-branch cases in the test covering
push_published to include a multi-segment branch such as oc/acme/a/b, asserting
it returns OpenCompanyError::InvalidRequest. Keep the existing valid-shaped
commit ID and error-message assertion unchanged so the test records the intended
rejection behavior in assert_publish_branch.
πŸͺ„ Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
βš™οΈ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: dc23c9e7-5be1-4857-8c1b-1cd00343aae4

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 8705dc5 and 891a7ba.

πŸ“’ Files selected for processing (13)
  • docs/spec/runtime/repos.md
  • src/harness/brain.rs
  • src/harness/build.rs
  • src/harness/policy.rs
  • src/harness/repo.rs
  • src/harness/repo/test.rs
  • src/policy/consequence.rs
  • src/runtime/cycle.rs
  • src/runtime/grants.rs
  • src/runtime/journal.rs
  • src/runtime/repo_manager.rs
  • src/runtime/repo_manager/test.rs
  • src/server/operator.rs

Comment thread src/harness/brain.rs
Comment thread src/harness/build.rs
Comment thread src/harness/repo.rs
Comment thread src/harness/repo.rs
Comment thread src/runtime/repo_manager.rs
Comment thread src/runtime/repo_manager/test.rs
YellowSnnowmann and others added 3 commits August 13, 2026 02:58
…nsai#796)

The per-turn checkout janitor deletes a checkout at every turn end, so
under `supervised` β€” where each write step parks for approval β€” the commit
`repo_publish` needs is wiped between the parked steps and the chain
deadlocks (issue tinyhumansai#796).

Give `CheckoutLedger` a task-keyed retained set the janitor does not touch:
`retain_for_task` moves a parked turn's checkout off the turn-scoped list,
`reclaim` brings it back for the resume, `purge_task` deletes it at task end,
and `sweep_orphans` reclaims the disk once no live grant names the task.
`repo_checkout` reuses a reclaimed tree (`has_active`) rather than re-cloning
over the agent's own commits.

The wiring that calls these lands next; this is the primitive plus its tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…yhumansai#796)

Carry the parked call's task on the grant (`GrantedCall::origin_task`,
`StandingGrant::origin_task`), stamped at mint from the approval's
`approval_task` join, and expose `GrantSet::any_for_task`.

The approval re-issue then stamps the resumed task on the ledger β€” so
`repo_publish` can name its branch, which tinyhumansai#735 could not do here β€” and
`reclaim`s the checkout the parked step left, so the resumed commit and
publish work on the same tree. Each janitor claim `sweep_orphans` any task
whose approval was denied or expired; a dispatch that parks holds its
checkout across the park. This is the "deleted at task end" tinyhumansai#245 Β§5 / tinyhumansai#247
Β§7 promised; a per-turn delete made it a deadlock under supervision.

Covered by two brain tests (reclaim-across-park, orphan-sweep) and the
ledger tests from the previous commit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…hats (tinyhumansai#796)

Two gaps the live supervised test surfaced:

1. Batched approvals. An operator commonly approves checkout, edit, commit and
   publish up front, so each grant is re-issued in its own turn parking nothing
   new. Retaining the checkout only on a turn that parked a fresh approval
   dropped the tree the very turn `repo_checkout` materialized it, so the next
   approved step hit "Not in a git repository" and the loop was back. Hold the
   task's checkout across EVERY re-issue instead; `sweep_orphans` reclaims it
   once no live grant names the unit.

2. DM chats. The write flow deadlocked identically in a DM, where there is no
   card. Treat the conversation as the work unit: a grant's `origin_task` now
   carries the card id OR the sanitized DM thread (`approval_work_key` +
   `sanitize_work_segment`, `dm-`-prefixed to a safe `oc/<company>/<unit>`
   segment). The entire task-scoped machinery β€” retention, reclaim, sweep and
   the publish branch β€” then covers a DM unchanged.

Tests: a two-re-issue batch keeps the checkout; sanitize maps a thread to a safe
segment. 205 touched-module tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@YellowSnnowmann
YellowSnnowmann force-pushed the fix/796-checkout-survives-park branch from 891a7ba to c71260e Compare August 12, 2026 21:32
@YellowSnnowmann
YellowSnnowmann marked this pull request as ready for review August 12, 2026 21:44
…d re-checkout (tinyhumansai#796)

Two review findings on the tinyhumansai#796 checkout-retention change:

- `sweep_orphans` keyed liveness on live grants alone, but a parked-yet-
  unresolved approval mints no grant until the operator decides it. In that
  window an unrelated turn's sweep could delete the checkout the parked step is
  holding for its own resume β€” the tinyhumansai#796 deadlock, reopened one turn upstream.
  The shared `GrantSet` now carries a `pending` set: `park` marks the work unit,
  settle/deny/expire clear it, and `any_for_task` treats a pending approval as
  live.

- `repo_checkout`'s reuse guard keyed on the repo alone, so a second checkout of
  the same repo at a different `ref` or `pr` silently returned the held tree. It
  now reuses only when the tree is on the requested ref/pr, naming the ref in the
  notice, and otherwise refuses β€” naming both the held ref and the requested one
  β€” rather than returning the wrong tree or cloning over the reclaimed commits. A
  resume re-issues the same call, so it still matches and reuses.

Tests: a parked approval survives an unrelated sweep and is reclaimed once
denied; a same-ref re-checkout reuses while a different ref/pr is refused.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@YellowSnnowmann

Copy link
Copy Markdown
Collaborator Author

@coderabbitai on the 6 nitpicks in the review summary:

  • repo/test.rs β€” tool-level test for the repo_checkout reuse branch: added in 500b0ebf (a_second_checkout_at_a_different_ref_is_refused_not_silently_reused). It drives the tool's execute path, and the same commit also fixes the underlying concern you flagged inline β€” that branch no longer drops ref/pr: it reuses only when the held tree is on the requested ref/pr and refuses otherwise.

The other five are anchored to the pre-rebase tip and land on code that isn't in this PR's post-rebase diff β€” src/runtime/repo_manager.rs (reuse first_line, --no-auto-gc), src/harness/build.rs (exact-set belt assertion), src/runtime/repo_manager/test.rs (pin the multi-segment case), and the stage_publish mirror-ref cleanup at repo.rs β€” all #735/#736 code merged to main. They're out of scope for #796; the substantive ones (denied/expired oc/<company>/<task> ref cleanup, --no-auto-gc) I'll take on the branch that owns that code rather than widening this one.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- cited commit ---'
git show --stat --oneline 500b0ebf
git show --format=fuller --find-renames --find-copies 500b0ebf -- src/harness/repo.rs src/harness/repo/test.rs

echo '--- current PR merge base and changed files ---'
base="$(git merge-base HEAD origin/main 2>/dev/null || git merge-base HEAD main)"
printf 'merge-base: %s\n' "$base"
git diff --name-status "$base"...HEAD

echo '--- current PR changes in named out-of-scope files ---'
git diff --stat "$base"...HEAD -- \
  src/runtime/repo_manager.rs \
  src/harness/build.rs \
  src/runtime/repo_manager/test.rs \
  src/harness/repo.rs

echo '--- checkout reuse implementation and test references ---'
ast-grep outline src/harness/repo.rs --items all --match 'repo_checkout|CheckoutLedger' --view expanded
rg -n -C 5 \
  'a_second_checkout_at_a_different_ref_is_refused_not_silently_reused|repo_checkout|reclaim|requested.*(ref|pr)|ref.*pr' \
  src/harness/repo.rs src/harness/repo/test.rs

Length of output: 42269


🏁 Script executed:

#!/bin/bash
set -euo pipefail

base="$(git merge-base HEAD origin/main 2>/dev/null || git merge-base HEAD main)"

echo '--- `#803` diff: stage_publish and mirror-ref cleanup context ---'
git diff -U20 "$base"...HEAD -- src/harness/repo.rs |
  rg -n -C 20 'stage_publish|oc/|mirror|publish|cleanup|delete|remove|refs/' || true

echo '--- current implementation context ---'
rg -n -C 20 'stage_publish\s*\(|oc/<|refs/oc|push_published|remove.*ref|delete.*ref' \
  src/harness/repo.rs src/runtime/repo_manager.rs

echo '--- introducing commits for relevant current lines ---'
git blame -L 1180,1280 -- src/harness/repo.rs 2>/dev/null || true

Length of output: 82304


@YellowSnnowmann, verification confirms the scope.

  • Commit 500b0ebf fixes the repo_checkout reuse behavior. The new tool-level test executes RepoCheckoutTool::execute. It verifies same-ref reuse and refusal for a different branch or pull request.
  • The current #803 diff does not modify src/runtime/repo_manager.rs, src/harness/build.rs, or src/runtime/repo_manager/test.rs.
  • The repo_publish and stage_publish code in src/harness/repo.rs predates the #803 merge base. The mirror-ref cleanup concern belongs with that existing #735/feat(repo): open a pull request host-side via the GitHub REST APIΒ #736 work.

I will treat the five remaining review-summary nitpicks as out of scope for #803. Would you like a follow-up GitHub issue for the denied/expired oc/<company>/<task> ref cleanup and --no-auto-gc work?


✏️ Learnings added
Learnt from: YellowSnnowmann
URL: https://github.com/tinyhumansai/opencompany/pull/803

Timestamp: 2026-08-12T22:18:23.859Z
Learning: For tinyhumansai/opencompany pull request `#803`, post-rebase review scope excludes pre-existing `#735/`#736 code in `src/runtime/repo_manager.rs`, `src/harness/build.rs`, `src/runtime/repo_manager/test.rs`, and the existing `repo_publish`/`stage_publish` flow in `src/harness/repo.rs`. Follow-up concerns about denied or expired `oc/<company>/<task>` mirror-ref cleanup and Git `--no-auto-gc` belong to the branch that owns that code.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: YellowSnnowmann
Repo: tinyhumansai/opencompany PR: 803
File: src/runtime/repo_manager/test.rs:1140-1167
Timestamp: 2026-08-12T22:14:28.479Z
Learning: For pull request `#803`, `src/runtime/repo_manager/test.rs` is pre-existing `#736` test code on `main` and is not part of the `#803` diff. The `checkout_with_commit` helper can explicitly clone `main`, but that change must be tracked separately from `#803`.

If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

You are interacting with an AI system.

@oxoxDev
oxoxDev merged commit 98882d6 into tinyhumansai:main Aug 13, 2026
17 checks passed
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.

bug: repo write tier deadlocks in supervised mode β€” checkout purged between parked steps (turn-scoped, not task-scoped)

2 participants