Skip to content

fix(repo): write-tier reliability β€” commit gpg-hang, push 300s hang, silent push failure (#815) - #817

Merged
oxoxDev merged 8 commits into
tinyhumansai:mainfrom
YellowSnnowmann:fix/815-write-tier-reliability
Aug 13, 2026
Merged

fix(repo): write-tier reliability β€” commit gpg-hang, push 300s hang, silent push failure (#815)#817
oxoxDev merged 8 commits into
tinyhumansai:mainfrom
YellowSnnowmann:fix/815-write-tier-reliability

Conversation

@YellowSnnowmann

@YellowSnnowmann YellowSnnowmann commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

Three write-tier reliability bugs, each of which independently stops an agent from publishing β€” all surfaced while validating #796 in the live console, and all confirmed fixed end to end (a real PR opened once the three landed). Fixed together per the tracking issue.

  1. Commit gpg-hang β€” the agent's checkout inherited the host operator's global commit.gpgsign = true, so every git_operations commit tried to GPG-sign and hung (no pinentry in the sandbox); the change staged but never committed. attribute_checkout now forces commit.gpgsign/tag.gpgsign to false (per-agent commit signing is the deferred feat(repo): sign agent commits with a per-agent identity key (deferred from #247)Β #738).
  2. Push 300s hang β€” git::run fed the PAT to the askpass on the child's one-shot stdin pipe, but a git push authenticates twice (ref-advertisement probe + pack upload). The second askpass invocation read EOF, and with the OS keychain deliberately disabled (GIT_CONFIG_NOSYSTEM, credential.helper= cleared) git-remote-https stalled to the timeout instead of failing. The askpass now reads the token from a 0600 file in its private $HOME, re-read on every prompt. (A fetch authenticates once, which is why only push hung β€” and why every hand-test masked it via the OS keychain.)
  3. Silent push failure β€” a failed push left the effect recorded as executed (the at-most-once guard) with no operator-facing note. perform_effect now posts a task note on push failure ("nothing reached the remote β€” re-run the task").

Review hardening

Fixes from the review of the three above:

  • Askpass files are created unpredictably and exclusively. The helper's $HOME was a predictable .askpass-<pid>-<seq> made with create_dir_all, and the token was written with std::fs::write, which follows a pre-planted token symlink before 0600 lands. The directory name is now 16 bytes from the OS CSPRNG created with an exclusive create_dir (0700 at creation), and the script and token are written with create_new at their mode β€” never following a symlink, never adopting an existing file.
  • DM work keys are injective. sanitize_work_segment folded every disallowed character to - (a keep-character), so coder/main and coder-main collided onto one work key. It now appends a build-stable digest of the raw thread when folding/trimming loses information; already-safe threads stay readable.
  • Publish-failure notes are classified and land only on real cards. The note embedded the raw git error (which can name host paths and the remote URL) in a durable, company-readable record; it now carries a fixed, classified sentence with the diagnosis kept to the log (workflows: an http_request node never consults ApprovalPolicy, so its requests raise no operator approvalΒ #614/publish: tell the truth about a stored payload, and record its digestΒ #688). A DM's repo_publish stamps a dm-* work key that no card owns, so the note is now posted only when the id resolves to a real card.

Base

Was stacked on #796; #796 (#803) has since merged, so this branch is now the write-tier reliability change on its own, merged up to current main.

Test plan

  • cargo fmt --all -- --check
  • cargo clippy --all-targets -- -D warnings (default)
  • cargo clippy --features openhuman,tinycortex --all-targets -- -D warnings
  • askpass file-helper answers the password on every prompt; exclusive dir/token creation, symlink rejection, and 0600 mode (unix)
  • checkout disables commit signing even when the host enables it
  • sanitize_work_segment collision + determinism; GrantSet pending-approval window; a publish note lands only on a real card, not a dm-* key
  • runtime::repo_manager::git + harness::repo + runtime::grants + runtime::cycle module tests pass
  • Verified live: full repo_checkout β†’ edit β†’ commit β†’ publish β†’ push β†’ PR in the console

Closes #815.

πŸ€– Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Preserved approved workspaces across workflow turns and reused valid checkouts without losing commits.
    • Added lifecycle handling to reclaim, purge, and clean up inactive workspaces.
    • Reported repository publishing failures directly on the associated task.
    • Improved handling of conversation work keys to prevent collisions.
  • Bug Fixes

    • Prevented checkout reuse with a different branch or pull request.
    • Secured Git credential handling against repeated prompts and unsafe file paths.
    • Disabled commit and tag signing for attributed checkouts.
    • Kept tasks active until all pending approvals are resolved.

@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: 37 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: 64751a5e-bde6-4e3c-9e26-b50588b7439b

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between f3a0475 and dcc9e2e.

πŸ“’ Files selected for processing (1)
  • src/runtime/repo_manager/git.rs

No actionable comments were generated in the recent review. πŸŽ‰

ℹ️ Recent review info
βš™οΈ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5161c9a0-ae5d-4770-8234-6278794b482f

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 5888969 and f3a0475.

πŸ“’ Files selected for processing (2)
  • src/runtime/cycle.rs
  • src/runtime/grants.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/runtime/grants.rs
  • src/runtime/cycle.rs

πŸ“ Walkthrough

Walkthrough

Checkout retention now preserves workspaces across approval turns and reuses matching refs. Git publishing uses private file-backed askpass credentials. Publish failures create classified notes on real task cards. Lossy DM keys receive stable digests.

Changes

Checkout lifecycle

Layer / File(s) Summary
Checkout retention and reuse
src/harness/repo.rs, src/harness/repo/test.rs
CheckoutLedger retains, reclaims, purges, and sweeps task checkouts. repo_checkout reuses matching active refs and refuses different refs.
Checkout attribution and approval liveness
src/harness/repo.rs, src/harness/repo/test.rs, src/runtime/grants.rs
Checkout attribution disables commit and tag signing. Tests verify agent identity and pending-approval task liveness.

Publishing reliability

Layer / File(s) Summary
Publish and credential execution
src/runtime/repo_manager/git.rs, src/runtime/cycle.rs
Git askpass reads a private token file for repeated prompts. Exclusive creation and restrictive permissions protect the credential. Publish failures use classified notes on real cards.
DM work-key sanitization
src/runtime/cycle.rs
Lossy DM keys append deterministic FNV-1a digests. Tests verify deterministic, collision-resistant output.

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

Mergeability Score: πŸ”΅ Low Β· up to f3a04

The change adds an operator-facing note and rerun guidance when publishing fails, but the write-tier specification does not yet document that behavior. The PR is mergeable with explicit owner follow-up to update the specification.

Sequence Diagram(s)

sequenceDiagram
  participant ApprovalWorkflow
  participant CheckoutLedger
  participant repo_checkout
  ApprovalWorkflow->>CheckoutLedger: retain_for_task(task)
  ApprovalWorkflow->>CheckoutLedger: reclaim(task)
  ApprovalWorkflow->>repo_checkout: request checkout ref
  repo_checkout->>CheckoutLedger: has_active(path)
  repo_checkout-->>ApprovalWorkflow: reuse matching checkout
Loading
sequenceDiagram
  participant PublishCycle
  participant AskpassDir
  participant Git
  participant TaskCard
  PublishCycle->>AskpassDir: write_token
  PublishCycle->>Git: spawn with GIT_ASKPASS
  Git->>AskpassDir: read token for each prompt
  PublishCycle->>TaskCard: append classified failure note
Loading

Possibly related PRs

Suggested labels: cluster:approvals, priority: p2, cluster:spine

Poem

A rabbit guards the checkout tree,
Keeps commits safe for all to see.
Tokens hide where prompts repeat,
Failed pushes leave notes complete.
Digests keep DM paths distinctβ€”
Hop, review, and conflicts glint! πŸ‡

πŸš₯ Pre-merge checks | βœ… 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The pull request includes DM work-key hash sanitization, which is unrelated to the three publishing fixes in issue #815. Move DM work-key sanitization to a separate pull request, or document its direct dependency on issue #815.
βœ… Passed checks (4 passed)
Check name Status Explanation
Description Check βœ… Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check βœ… Passed The title clearly summarizes the three write-tier reliability fixes addressed by the pull request.
Linked Issues check βœ… Passed The changes address all three requirements in issue #815: unsigned agent commits, reusable secure askpass credentials, and visible push-failure notes.
Docstring Coverage βœ… Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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 added cluster:spine The spine epic: prompt to delivered output priority: p2 Soon. Real but survivable β€” a rough edge, a gap, a thing that will bite later. labels Aug 12, 2026
@YellowSnnowmann
YellowSnnowmann force-pushed the fix/815-write-tier-reliability branch from 24138ac to 4dad61a Compare August 12, 2026 21:38
@YellowSnnowmann
YellowSnnowmann marked this pull request as ready for review August 12, 2026 21:44

@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: 2

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

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

Add a tool-level checkout reuse test.

Invoke RepoCheckoutTool::execute twice and confirm that the second call preserves changes in the existing checkout instead of re-materializing it.

πŸ€– 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 857 - 881, Add a tool-level test around
RepoCheckoutTool::execute that performs two checkout calls for the same task and
destination, modifies the checkout after the first call, then verifies the
second call reuses the existing directory and preserves the modification instead
of re-materializing it.

Source: Coding guidelines

πŸ€– 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/runtime/cycle.rs`:
- Around line 929-956: Make conversation-derived work keys collision-resistant
in approval_work_key and the corresponding thread-key path around the other
affected usage. Compute a short stable hash from the original unsanitized
thread, then incorporate it into the sanitized segment so separator variants
remain distinct while retaining a safe branch segment. Leave task-card IDs
unchanged and ensure the same derived value is used by origin_task,
CheckoutLedger, and repo_publish.

In `@src/runtime/repo_manager/git.rs`:
- Around line 193-214: Harden the askpass setup around the directory-creation
logic and write_token: generate the directory name with getrandom::fill, create
it exclusively with create_dir, and replace std::fs::write with
OpenOptions::create_new(true), applying mode 0600 before writing the token. Add
Unix-specific tests covering exclusive directory creation, token symlink
rejection, and the resulting file permissions.

---

Nitpick comments:
In `@src/harness/repo.rs`:
- Around line 857-881: Add a tool-level test around RepoCheckoutTool::execute
that performs two checkout calls for the same task and destination, modifies the
checkout after the first call, then verifies the second call reuses the existing
directory and preserves the modification instead of re-materializing it.
πŸͺ„ 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: 8ccfae58-a57b-402d-886f-a1e707d05eb0

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between ea8579e and 24138ac.

πŸ“’ Files selected for processing (11)
  • docs/spec/runtime/repos.md
  • src/harness/brain.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/git.rs
  • src/server/operator.rs

Comment thread src/runtime/cycle.rs
Comment thread src/runtime/repo_manager/git.rs Outdated
YellowSnnowmann and others added 4 commits August 13, 2026 03:55
…ai#815)

An agent's checkout inherits the host operator's global git config, so on a
host with `commit.gpgsign = true` every `git commit` blocks on a GPG key the
sandbox cannot reach β€” the change stages but never commits and the write flow
stalls with nothing published. `attribute_checkout` now forces
`commit.gpgsign` / `tag.gpgsign` to `false` alongside the agent-seat identity;
per-agent commit signing is the deferred issue tinyhumansai#738.

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

git invokes the askpass helper once per authenticated request, and a `git push`
authenticates TWICE (ref-advertisement probe + pack upload). The token was fed
on the child's stdin β€” a one-shot pipe β€” so the second prompt read EOF and, with
the OS keychain deliberately disabled here, `git-remote-https` stalled to the
300s network timeout instead of failing. The write tier's push hung for exactly
this reason; a fetch (one auth) never showed it, and any hand-test fell back to
the OS keychain and masked it.

Write the token into a 0600 file in the helper's private `$HOME` instead, read
on every prompt. Still never in argv, environment, git config or a URL β€” only
briefly in a per-invocation file removed on drop.

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

A failed host-side push leaves the effect recorded as executed (the at-most-once
guard), so re-approving is a no-op. With no operator-facing note the change sits
staged in the mirror, nothing reaches the remote, the approval is gone, and the
operator believes it published. Post a task board note on push failure so they
know nothing landed and the way back is to re-run the task.

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

Two review findings on the write-tier reliability change:

- The askpass helper's `$HOME` was a predictable `.askpass-<pid>-<seq>` made with
  `create_dir_all`, and the token was written with `std::fs::write` β€” which
  follows a pre-planted `token` symlink before the 0600 mode lands. A local
  attacker sharing the repo cache could pre-create either and capture the PAT.
  The directory name is now 16 bytes from the OS CSPRNG created with an
  exclusive, non-recursive `create_dir` (0700 at creation), and both the script
  and the token are written with `create_new` at their mode β€” never following a
  symlink, never adopting an existing file.

- `sanitize_work_segment` folds every disallowed character to `-`, itself a
  keep-character, so `coder/main` and `coder-main` reduced to the same DM work
  key β€” colliding checkout retention and the publish branch. When folding or
  trimming loses information it now appends a short build-stable digest of the
  raw thread, so distinct threads keep distinct keys; an already-safe thread is
  unchanged and stays readable.

Tests: unix coverage for exclusive dir/token creation, symlink rejection, and
0600 mode; a sanitize collision + determinism case.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@YellowSnnowmann
YellowSnnowmann force-pushed the fix/815-write-tier-reliability branch from 4dad61a to 5888969 Compare August 12, 2026 22:36
@YellowSnnowmann

Copy link
Copy Markdown
Collaborator Author

@coderabbitai on the nitpick β€” a tool-level test for the repo_checkout reuse branch (repo.rs ~857-881): that test now exists in this PR's diff. It came in with the rebased #796 base (500b0ebf): a_second_checkout_at_a_different_ref_is_refused_not_silently_reused drives RepoCheckoutTool::execute through the reuse path β€” asserting same-ref reuse, a different-branch refusal, and a pr-over-branch refusal β€” and the same commit fixed that branch to stop dropping ref/pr. So this is covered.

@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

ast-grep outline src/harness/repo.rs --items all --type function,method --match 'a_second_checkout_at_a_different_ref_is_refused_not_silently_reused|execute' || true

rg -n -A90 -B20 \
  'a_second_checkout_at_a_different_ref_is_refused_not_silently_reused|fn execute\s*\(' \
  src/harness/repo.rs

printf '\n--- relevant diff history ---\n'
git show --stat --oneline 500b0ebf -- src/harness/repo.rs 2>&1 || true
git show --format= --find-renames 500b0ebf -- src/harness/repo.rs 2>&1 | sed -n '1,260p' || true

Length of output: 25208


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -A180 -B30 \
  'a_second_checkout_at_a_different_ref_is_refused_not_silently_reused' \
  .

printf '\n--- test module map ---\n'
fd -t f 'test\.rs$|repo.*test.*\.rs$|.*repo.*\.rs$' src/harness | sort

printf '\n--- direct RepoCheckoutTool::execute calls in harness tests ---\n'
rg -n -A8 -B8 \
  'RepoCheckoutTool|\.execute\(json!\(\{.*"repo"' \
  src/harness -g '*.rs'

Length of output: 36271


@YellowSnnowmann, confirmed. a_second_checkout_at_a_different_ref_is_refused_not_silently_reused invokes RepoCheckoutTool::execute through the registered repo_checkout tool.

The test covers same-ref reuse, different-branch refusal, and PR-over-branch refusal. It also verifies the ref and pr values in the tool result. The nitpick is withdrawn.


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

Timestamp: 2026-08-12T22:38:18.788Z
Learning: In `src/harness/repo/test.rs`, `a_second_checkout_at_a_different_ref_is_refused_not_silently_reused` is a tool-level test that invokes `RepoCheckoutTool::execute` through the registered `repo_checkout` tool. It verifies same-ref reuse and refusal when a held checkout is requested at a different branch or pull request.

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.

You are interacting with an AI system.

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/spec/runtime/repos.md (1)

482-494: πŸ—„οΈ Data Integrity & Integration | 🟑 Minor | ⚑ Quick win

Document host-side push failures.

The repo_publish description documents task reporting when pull-request creation fails, but it does not document the new host-side push-failure contract. Add that a failed push posts a task discussion, states that no commit reached the remote, and tells the operator to rerun the task.

Proposed documentation update
   - `repo_publish` (issue `#735`). The agent commits locally in its checkout,
     then publishes host-side. The host *fetches* the checkout's committed HEAD
     into the mirror on a host-owned `oc/<company>/<task>` branch β€” a fetch never
     invokes `receive-pack`, so the read tier's `pre-receive` refusal and its
     no-push contract test stay untouched β€” and, only after the operator approves,
     pushes that exact commit to the remote. The agent still holds no credentialed
     remote and never pushes; every structural refusal (host-generated namespaced
     branch, never a force push, never the default branch, never a ref outside
     `oc/`) lives in `RepoManager` where no prompt can reach it.
+    If the host-side push fails, `repo_publish` posts a task discussion stating
+    that no commit reached the remote and that the task must be rerun.

This matches the PR objective for push-failure task reporting.

πŸ€– 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 `@docs/spec/runtime/repos.md` around lines 482 - 494, Update the repo_publish
documentation to describe host-side push failures: the task must post a
discussion stating that no commit reached the remote and instructing the
operator to rerun the task. Keep the existing pull-request failure behavior
unchanged.
🧹 Nitpick comments (1)
src/runtime/grants.rs (1)

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

Add a unit test for the pending window in this module.

mark_pending, clear_pending, and the third arm of any_for_task carry the whole #796 guarantee, and this module's mod test covers none of them. The existing fixtures were only updated with origin_task: None.

A small test pins the three transitions that matter: a pending mark alone makes any_for_task true, clear_pending makes it false, and a second approval on the same task clears independently.

The repository guidelines require focused tests with every behavior change and keep package-local tests in the module file.

πŸ§ͺ Suggested test
/// Issue `#796`: the window between a park and the operator's decision.
#[test]
fn a_still_parked_approval_keeps_its_task_alive() {
    let set = GrantSet::default();
    assert!(!set.any_for_task("t-1"));

    set.mark_pending(&ApprovalId::new("a1"), "t-1".to_string());
    assert!(
        set.any_for_task("t-1"),
        "a parked approval mints no grant, so only the mark can hold the checkout"
    );

    // A second step of the same task, cleared independently.
    set.mark_pending(&ApprovalId::new("a2"), "t-1".to_string());
    set.clear_pending(&ApprovalId::new("a1"));
    assert!(set.any_for_task("t-1"), "the task's other step is still parked");

    set.clear_pending(&ApprovalId::new("a2"));
    assert!(!set.any_for_task("t-1"), "nothing names it now, so it is sweepable");

    // A live grant is the other arm, and it must not be confused with the mark.
    set.grant(GrantedCall {
        origin_task: Some("t-2".to_string()),
        ..call("a3", "ops", "shell", serde_json::json!({}))
    });
    assert!(set.any_for_task("t-2"));
    assert!(set.consume("ops", "shell", &serde_json::json!({})).is_some());
    assert!(!set.any_for_task("t-2"), "a spent grant names nothing");
}
πŸ€– 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/grants.rs` around lines 528 - 578, Add a focused unit test in the
module’s existing mod test for the pending-approval lifecycle, using
mark_pending, clear_pending, and any_for_task: verify one pending approval keeps
its task alive, independently clearing one of two approvals leaves it alive,
clearing both makes it false, and preserve the existing live-grant/consume
behavior to confirm spent grants no longer keep a task alive.

Source: Coding guidelines

πŸ€– 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/runtime/cycle.rs`:
- Around line 1338-1362: Update both repository publish failure branches around
repos.push_published so TaskDiscussionPosted is emitted only after resolving
payload["task"] to an existing card ID, rather than using the resumed DM work
key from task. When the task is a dm-* key or no card resolves, report the
failure through the existing operator channel instead of appending a discussion
to a nonexistent card, while preserving the returned publish error.

---

Outside diff comments:
In `@docs/spec/runtime/repos.md`:
- Around line 482-494: Update the repo_publish documentation to describe
host-side push failures: the task must post a discussion stating that no commit
reached the remote and instructing the operator to rerun the task. Keep the
existing pull-request failure behavior unchanged.

---

Nitpick comments:
In `@src/runtime/grants.rs`:
- Around line 528-578: Add a focused unit test in the module’s existing mod test
for the pending-approval lifecycle, using mark_pending, clear_pending, and
any_for_task: verify one pending approval keeps its task alive, independently
clearing one of two approvals leaves it alive, clearing both makes it false, and
preserve the existing live-grant/consume behavior to confirm spent grants no
longer keep a task alive.
πŸͺ„ 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: 0abe5a93-44fa-4156-81b2-392b7a3bb656

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 24138ac and 5888969.

πŸ“’ Files selected for processing (11)
  • 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/runtime/repo_manager/git.rs
  • src/server/operator.rs
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/harness/repo/test.rs
  • src/policy/consequence.rs
  • src/runtime/journal.rs
  • src/server/operator.rs
  • src/harness/brain.rs
  • src/harness/repo.rs

Comment thread src/runtime/cycle.rs

@oxoxDev oxoxDev left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

All three diagnoses are excellent, and the second is the best piece of debugging I have read in this repo:

git push authenticates twice (ref-advertisement probe + pack upload). The second askpass invocation read EOF, and with the OS keychain deliberately disabled … git-remote-https stalled to the timeout instead of failing. … A fetch authenticates once, which is why only push hung β€” and why every hand-test masked it via the OS keychain.

That last clause is the whole reason this survived #735's review, including mine. A one-shot stdin pipe answering a prompt that fires twice is invisible to anyone whose machine has a keychain to fall back on.

The token handling in the fix is careful, and I checked it rather than taking it: the helper script itself carries no credential (so nothing sensitive lands in ps or in the script on disk), the token is a 0600 file in a per-operation scratch $HOME that is removed when dropped, HOME is redirected so the operator's own .gitconfig cannot be resolved, and GIT_TERMINAL_PROMPT=0 with no askpass installed is the fail-closed path when there is no credential at all.

Forcing commit.gpgsign/tag.gpgsign to false in attribute_checkout is right: an agent's host-side commit has no business being signed with the operator's key, and inheriting a global that expects pinentry in a sandbox is a hang rather than a failure. Deferring per-agent signing to #738 rather than improvising it here is the correct scope.

1 major. Requesting changes β€” the fix is one line and I would rather it ship correct than ship twice.

Major β€” a raw subprocess error goes into a durable, operator-facing record

let note = format!(
    "Could not publish `{branch}` to the remote: {err}. Nothing reached the \
     remote, and this approval will not retry on its own β€” re-run the task to \
     publish again."
);

{err} is the unfiltered error from a git child process, and this note is posted as a TaskDiscussionPosted β€” durable, and readable by any company member.

Git's failure text routinely carries the remote URL and local filesystem paths: the scratch $HOME the askpass helper lives in, and the checkout path. That is precisely what this codebase has already decided not to do, in the same subsystem, one PR over β€” #688's PayloadStorage::Refused deliberately excludes the store's error text from the artifact body, with the reason written at the site: "this string is permanent, and a backend error can name host paths; the operator needs to know the file is not there, and the diagnosis belongs in the log." #614 is the older precedent, where a URL's userinfo reached the durable approval journal.

I do not think the token is reachable here β€” it travels through askpass and never appears in a URL, which is exactly why that design is good. So this is host paths and remote URLs rather than a credential leak, which is why I am flagging it as Major and not Critical.

The fix is the shape #688 already established: keep tracing::warn!(branch, "...: {err}") exactly as it is β€” the diagnosis belongs in the log β€” and give the note a classified sentence instead of the raw text. The operator needs three facts, none of which require {err}: the branch did not reach the remote, the approval will not retry, and re-running the task is the way back. All three are already in your sentence.

If the team wants the two hang fixes on main today, this does not have to hold them up β€” swapping {err} for a fixed clause is a one-line change, not a redesign.

Also worth fixing before merge

The description's SHAs are stale. It lists b15c5da5 as the push-failure commit; the branch has 24138ac4. Harmless in itself, but this repo has lost review fixes twice this week to a merge landing against a head someone believed was current, so a description that names commits which are not on the branch is worth correcting rather than leaving.

144 commits behind, and stacked on #803 which is not on main. The stacking is stated clearly, which is the right way to present it β€” but the net change here wants re-verifying against main once #803 lands, not just against its current parent.

Answered in the code, and I want to note it approvingly

I went looking for whether "re-run the task" is actually possible given the at-most-once guard, and the comment answers it before the question can be asked:

A failed push leaves the effect recorded as executed (the at-most-once guard), so re-approving is a no-op; the operator has to KNOW it failed, and that the way back is to re-run the task, or the publish vanishes with the change staged in the mirror and nothing on the remote.

Stating the recovery path and why the obvious one does not work is what makes the note trustworthy rather than reassuring.

YellowSnnowmann and others added 3 commits August 13, 2026 12:31
…ds (tinyhumansai#815)

Two review findings on the publish-failure board notes:

- The note embedded the raw `git` error, which routinely names the remote URL
  and host paths (the askpass scratch $HOME, the checkout) β€” in a record that is
  durable and readable by any company member. It now carries a fixed, classified
  sentence (the branch did not reach the remote, the approval will not retry,
  re-run the task); the diagnosis stays in the `tracing::warn`, where a
  host-path-bearing error belongs (the rule tinyhumansai#614/tinyhumansai#688 set). Applies to both the
  push-failure and the PR-open-failure note.

- A DM's `repo_publish` stamps its `dm-*` work key as the effect's `task`, which
  no card owns, so `TaskDiscussionPosted` filed the note against a phantom card.
  The note is now posted only when the id resolves to a real card; otherwise the
  failure stays in the operator log.

Test: `task_names_a_card` matches a real card and rejects a `dm-*` key, an
unknown id, and the empty id.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
mark_pending/clear_pending and the pending arm of any_for_task carry the
tinyhumansai#796 guarantee but had no direct unit coverage in this module β€” the sweep
test in harness::repo exercised them only through the ledger. Pin the three
transitions: a pending mark alone keeps a task live, clearing it drops it,
and two approvals on one task clear independently.

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

Copy link
Copy Markdown
Collaborator Author

@coderabbitai on the two summary items:

  • grants.rs β€” unit test for the pending window: added in 47fe3ac (a_still_parked_approval_keeps_its_task_alive), pinning the three transitions directly on GrantSet β€” a pending mark alone makes any_for_task true, clear_pending drops it, and two approvals on one task clear independently.
  • repos.md β€” document the push-failure contract: valid, but docs/spec/runtime/repos.md is exactly at the repo's 500-line cap (CLAUDE.md), and fitting the note would mean cutting the structural-refusal enumeration or splitting the file β€” disproportionate for a one-line note. The contract is documented at the code instead: the note_publish_failure_on_card doc comment and the classified note text. I'll fold it into the spec when that file is next split.

@YellowSnnowmann

Copy link
Copy Markdown
Collaborator Author

@oxoxDev β€” thank you, and all three points are addressed.

Major β€” raw git error in the durable note. Fixed in 92520d0. Both notes (push failure, and the #736 PR-open failure) now carry a fixed, classified sentence β€” nothing reached the remote, the approval will not retry, re-run the task β€” and the raw {err} stays in tracing::warn! exactly as before. This is the shape you pointed at from #688: the diagnosis belongs in the log, the durable record gets the three facts. While there I also closed a second leak vector CodeRabbit flagged on the same note: a DM's repo_publish stamps a dm-* work key that no card owns, so the note now posts only when the id resolves to a real card.

Stale SHAs in the description. Corrected β€” I rewrote the description to describe the changes by content rather than pin SHAs that a rebase invalidates (the exact failure you flagged), and added the review-hardening section.

144 behind / stacked on #803. #803 (#796) merged at 05:20 today, so this is now the write-tier reliability change on its own. I've merged current main into the branch (f3a0475) and re-verified against it β€” fmt, default clippy, and --features openhuman,tinycortex clippy all green, plus the module tests.

Appreciate the careful read on the token handling β€” that was the part I most wanted a second set of eyes on.

@coderabbitai

coderabbitai Bot commented Aug 13, 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

printf '%s\n' '--- repository guidance mentioning the documentation cap ---'
rg -n -C2 '500.?line|500 lines|repos\.md' CLAUDE.md 2>/dev/null || true

printf '%s\n' '--- target file line count ---'
wc -l docs/spec/runtime/repos.md

printf '%s\n' '--- GrantSet and pending-window test map ---'
ast-grep outline src/runtime/grants.rs --items all --type function,method --match 'any_for_task|mark_pending|clear_pending|a_still_parked_approval_keeps_its_task_alive' --view expanded || true

printf '%s\n' '--- pending-window test and GrantSet implementation ---'
rg -n -A130 -B25 \
  'a_still_parked_approval_keeps_its_task_alive|fn (mark_pending|clear_pending|any_for_task)\b' \
  src/runtime/grants.rs

printf '%s\n' '--- push-failure code documentation and classified note text ---'
rg -n -A75 -B25 \
  'note_publish_failure_on_card|nothing reached the remote|approval will not retry|re-run the task' \
  src

printf '%s\n' '--- claimed test commit ---'
git show --format=fuller --stat 47fe3acf -- src/runtime/grants.rs 2>&1 || true

Length of output: 30370


@YellowSnnowmann, confirmed.

a_still_parked_approval_keeps_its_task_alive directly verifies all three GrantSet transitions. The pending-window test item is withdrawn.

docs/spec/runtime/repos.md is exactly at the required 500-line limit. note_publish_failure_on_card and its call site document the current push-failure contract: the effect does not retry, the operator must re-run the task, and the durable note excludes raw Git errors. Deferring the spec addition until the file is split is reasonable. This item is withdrawn.

πŸ‡


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

Timestamp: 2026-08-13T07:12:54.432Z
Learning: In this repository, `CLAUDE.md` requires every Markdown file to have 500 lines or fewer. `docs/spec/runtime/repos.md` is at that limit. The `repo_publish` host push-failure contract is currently documented in `src/runtime/cycle.rs` at `note_publish_failure_on_card` and its call site: the at-most-once effect does not retry, operators must re-run the task, and durable task discussion text must be fixed and classified without raw Git errors.

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.

You are interacting with an AI system.

@coderabbitai coderabbitai Bot added the cluster:approvals Human-in-the-loop boundary, policy, and approval surfaces label Aug 13, 2026
…ai#815)

The exclusivity test asserted the random dir name does not *contain* the pid
as a substring β€” flaky, since a 32-char hex string can hold the pid's decimal
digits by chance (CI hit exactly that: .askpass-db1ab4f4...). Pin the real
property instead: a full-width hex suffix, and a second create landing on a
different name.

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

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

tinysweeper found nothing blocking. Approving.

             $0.0439 Β· 47,890 in / 12,586 out Β· 35,745 cached (75%) Β· z-ai/glm-5.2
critique:    $0.0143 Β· 9,080 in  / 4,858 out  Β· 6,509 cached (72%)  Β· z-ai/glm-5.2
security:    $0.0094 Β· 9,059 in  / 2,731 out  Β· 6,388 cached (71%)  Β· z-ai/glm-5.2
tests:       $0.0110 Β· 14,308 in / 3,036 out  Β· 11,431 cached (80%) Β· z-ai/glm-5.2
description: $0.0093 Β· 15,443 in / 1,961 out  Β· 11,417 cached (74%) Β· z-ai/glm-5.2

@tinysweeper

tinysweeper Bot commented Aug 13, 2026

Copy link
Copy Markdown

What this change touches

5 files, +513 -134 across 4 components. It reaches 6 untouched components (60 graph nodes walked).

flowchart LR
  n0["src/runtime/repo_manager<br/>1 file +265 -101"]:::changed
  n1["src/runtime<br/>2 files +211 -32"]:::changed
  n2["src/harness/repo<br/>1 file +24 -0"]:::changed
  n3["src/harness<br/>1 file +13 -1"]:::changed
  n4["src/runtime<br/>4 files reached"]:::impacted
  n5["src/ports<br/>3 files reached"]:::impacted
  n6["src/company<br/>2 files reached"]:::impacted
  n7["src<br/>1 file reached"]:::impacted
  n8["src/harness<br/>1 file reached"]:::impacted
  n9["src/server<br/>1 file reached"]:::impacted
  n4 -->|34 refs| n5
  n4 -->|12 refs| n6
  n4 -->|8 refs| n7
  n6 -->|5 refs| n4
  n6 -->|3 refs| n5
  n5 -->|2 refs| n7
  n6 -->|2 refs| n7
  n8 -->|2 refs| n5
  n9 -->|2 refs| n4
  n9 -->|2 refs| n5
  n9 -->|2 refs| n6
  n5 -->|1 ref| n6
  n7 -->|1 ref| n4
  n7 -->|1 ref| n5
  n7 -->|1 ref| n6
  n8 -->|1 ref| n4
  n8 -->|1 ref| n6
  n9 -->|1 ref| n7
  classDef changed fill:#0d4429,stroke:#238636,color:#e6edf3
  classDef impacted fill:#161b22,stroke:#6e7681,color:#c9d1d9
  classDef flagged fill:#5a1e02,stroke:#d93f0b,color:#ffffff
  classDef blocking fill:#67060c,stroke:#f85149,color:#ffffff
Loading

Green: changed. Grey: untouched, reached through an import or a call. Orange: has findings. Red: has a finding that blocks the merge.

Component Files Lines Findings
src/runtime/repo_manager changed 1 +265 -101 β€”
src/runtime changed 2 +211 -32 β€”
src/harness/repo changed 1 +24 -0 β€”
src/harness changed 1 +13 -1 β€”
src/runtime reached 4 β€” β€”
src/ports reached 3 β€” β€”
src/company reached 2 β€” β€”
src reached 1 β€” β€”
src/harness reached 1 β€” β€”
src/server reached 1 β€” β€”
Changed files

src/runtime/repo_manager

  • src/runtime/repo_manager/git.rs

src/runtime

  • src/runtime/cycle.rs
  • src/runtime/grants.rs

src/harness/repo

  • src/harness/repo/test.rs

src/harness

  • src/harness/repo.rs

tinysweeper 0.1.0

@tinysweeper tinysweeper Bot added priority: p3 Whenever. Cosmetic, a nicety, or a cleanup with no user visible effect. and removed priority: p2 Soon. Real but survivable β€” a rough edge, a gap, a thing that will bite later. labels Aug 13, 2026
@oxoxDev
oxoxDev merged commit 54cbafa into tinyhumansai:main Aug 13, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cluster:approvals Human-in-the-loop boundary, policy, and approval surfaces cluster:spine The spine epic: prompt to delivered output priority: p3 Whenever. Cosmetic, a nicety, or a cleanup with no user visible effect.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: repo write tier can't publish β€” commit gpg-hang, push 300s hang, silent push failure

2 participants