Skip to content

Stop work on issues edited by untrusted accounts after ready-for-agent - #843

Merged
JacobStephens2 merged 4 commits into
mainfrom
issue-668
Oct 19, 2026
Merged

JacobStephens2 merged 4 commits into
mainfrom
issue-668

Conversation

@JacobStephens2

@JacobStephens2 JacobStephens2 commented Oct 19, 2026 •

Copy link
Copy Markdown
Owner

Summary

untrusted edit = userContentEdits (body) | RenamedTitleEvent (title)
                 by an account that is not a Trusted account (or a deleted one)

Ready issue search   edits since the latest ready-for-agent labelling
                     → pass over: "#7 body|title edited after it was marked ready by an account that is not a Trusted account"
Spec Ticket start    edits since later of Ticket's / Spec's last Trusted labelling (every edit if neither)
                     → stop before starting the Ticket's Run
each Run session     edits since the Run started (less a 5-minute margin)
  (implement, Resume,  → fail the Run: "#7 body edited since the Run started …; starting no further session"
   Repair, Security review, Spec review)
 Delivery::deliver
-  Sessions::within(logs, worktree, harness, steps)
+  Sessions::checked_within(logs, worktree, harness, no_untrusted_edit_since, steps)
     Sessions::start
+      before_each()?        # covers Resumes too
       run_session

The Trusted account check stays the one from #667. Untrusted edits now fail the Run before a Security review instead of becoming a Security hold. Trusted edits never count. A Trusted account applying ready-for-agent again resets the comparison point, and the existing settle wait still applies. Comments are out of scope.

Closes #668

Evidence

Each regression failed before its code and passes after:

Regression Before After
Pickup: outsider body/title edit after label Takes the issue Passes it over with the edit line
Pickup: edit by an account GitHub no longer knows Takes the issue Passes it over
Merge run: outsider edit during implement, CI red Starts the Repair Fails; no Repair session
Merge run: outsider edit, background work killed Starts the Resume Fails; no Resume session
Security review: outsider edit during implement "Security review incomplete" hold; no-merge exits 0 Run fails with the edit line
Merge run: edit dated 30 s before local Run start Missed; Repair runs Fails; no Repair session
Spec run: outsider title edit after Spec's labelling Starts Ticket #21 Stops: "Ticket #21: title edited after it was marked ready …"
cargo test --test pickup_run edit
cargo test --test merge_run under_way
cargo test --test merge_run an_untrusted_edit_dated_just_before_the_runs_local_start_still_stops_repairs
cargo test --test security_review an_untrusted_edit_before_the_security_review_fails_the_run
cargo test --test spec_run ticket_edited

These pass alongside the regressions: a Trusted edit or a Trusted relabel keeps the issue Ready, a Trusted edit mid-Run lets Repairs run, and a Ticket edited before its Spec's Trusted labelling starts.

Validation:

  • cargo test --no-fail-fast -- --test-threads=4 on 5480234 (before merging main): 2004 passed, 0 failed, 2 ignored across 52 result groups.
  • After merging main: cargo clippy --all-targets -- -D warnings passed, plus the unit tests, merge_run, security_review and packaging.

Test seam: the thirdshift CLI with real Git repositories and fake gh/Harness executables. The fake gh gains an edits state key and gh fake edits.

Review against main: Standards found 10 judgement calls and Spec found 3 findings. P1 and P2 failed when their tests were run as written; both are fixed and their tests are kept. Both axes read all 11 files changed at review time, through their diffs at least. Changed files left unread: src/github.rs (changed after the review, by the S1 extraction) and tests/security_review.rs (P1's test, added after the review). The rename to GLOSSARY.md came from merging main; the reviewed CONTEXT.md hunk carries over unchanged, apart from P2's wording.

Merge Danger

Door: two-way

Reverting restores #667's selection rule. Nothing persisted changes.

Blast Radius: dispatch

Every session of a Run now makes two extra GraphQL reads first, plus membership checks for any editor who isn't the owner. A failed GitHub read fails the Run before the session, after the existing transient retries. Direct thirdshift <Issue URL> runs still only warn at start, but an untrusted edit made during the Run now fails them too.

Unaddressed findings

Standards

  • S5, Primitive Obsession in the (IssueText, query, field) table (src/github/trust.rs, untrusted_edit). Declined. The table is the only place the triple appears; methods on the enum would spread one two-row table across three functions.
  • S6, Sessions::within → checked_within Middle Man. Declined. The reviewer called it acceptable: the check must sit in Sessions::start to cover Resumes, and the no-op within serves the Architect, Security and Pass sessions, which have no issue to check.
  • S7, no_untrusted_edit_since creates GitHub::new() (src/delivery.rs). Declined. This matches the idiom of src/spec_run.rs:273 and InWorktree::issue_is_open, and the CLI-level tests in tests/merge_run.rs and tests/security_review.rs exercise it.
  • S8, created_at fixture churn. Partly addressed: the fake's trust_events doc now says events carry created_at. Declined the helper: the fixtures are JSON literals, and their explicit times are what the new ordering tests read.
  • S10, ready-label scans don't share the login cache (issue_trust, trusted_ready_at). Declined. Each makes at most one membership call per labelling actor, and the owner short-circuits with no call. This is not a behaviour issue.
  • S1, S2, S3, S4 and S9 were addressed (shared paged_nodes, IssueText, time_at, no_untrusted_edit_since, edits_issue_7).

Spec

  • P3, the check also runs before a Run's first session. The spec says "Before each later session". Declined. Checking the first session is a superset: it also covers an edit made between the trust check and the implement session, and it costs two reads. The glossary states the behaviour as built.
  • P1 and P2 were addressed (see Evidence).

🤖 Generated with Claude Code

Built with claude · claude-opus-5-5 · medium

Security review outcome

No unaddressed introduced findings.

JacobStephens2 and others added 4 commits October 19, 2026 15:02
A body edit (userContentEdits) or title edit (RenamedTitleEvent) by an
account that is not a Trusted account, made after the most recent
ready-for-agent labelling, makes an issue not Ready. A Spec run checks
each Ticket against the later of the Ticket's and its Spec's last
labelling by a Trusted account. Before each session of a Run, a Resume
included, the issue is checked again for untrusted edits made since the
Run started, and the Run fails instead of starting the session.

Closes #668

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Address review findings: an untrusted edit found before the Security
review session failed it into a generic incomplete hold, letting the Run
go on; it now fails the Run. Count edits from a few minutes before the
Run started, covering the gap since the trust check and clock lag. Share
the GraphQL paging loop, rename Edit to IssueText, and add a test helper
for fake edits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
macOS date has no -d, so the edit's date came out empty there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@JacobStephens2
JacobStephens2 merged commit 25e4b8f into main Oct 19, 2026
18 checks passed
@JacobStephens2
JacobStephens2 deleted the issue-668 branch October 19, 2026 21:32
@JacobStephens2 JacobStephens2 mentioned this pull request Oct 20, 2026
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.

An untrusted edit after ready-for-agent makes an issue not Ready

1 participant