Skip to content

The Sessions module owns its Resume rules, tested in-process behind a seam - #408

Merged
JacobStephens2 merged 2 commits into
mainfrom
issue-407
Oct 5, 2026
Merged

JacobStephens2 merged 2 commits into
mainfrom
issue-407

Conversation

@JacobStephens2

Copy link
Copy Markdown
Owner

Summary

The Resume rules now reach the machine only through a private Outside seam, as the Claim, Delivery, Run, CI watch and Base fix rules already do.

 Sessions::within(logs, worktree, harness, steps)   # public interface unchanged
   skills::link_into(worktree)
-  steps(&Sessions { worktree, harness, … })
+  Sessions::taking(logs, Box::new(OnMachine { worktree, harness }), steps)
+    steps(&Sessions { outside: RefCell<Box<dyn Outside>>, … })
   last log, filtered to one that exists

 Sessions::run_to_final_message                      # the Resume rules
   start(kind)
-    progress::step("logging the session to …")
-    spawn CLI → Box<dyn Stream>
+    outside.step("logging the session to …")
+    outside.run_session(kind, resume, prompt, log) → Ended { session_id, killed, final_message }
   killed work + session id → one Resume as <kind>-resume
   still killed work → "carrying on" line, ending recorded
trait Outside            # private to src/session.rs
├── run_session(kind, resume, prompt, log) -> Result<Ended>
└── step(line: String)
OnMachine                # the real adapter: argv, transcript reader, process group, stream → Session log, stop signals
Scripted                 # the test adapter: scripted endings, records each call

OnMachine owns a clone of the worktree path and the Harness Choice. If it held borrows, RefCell<Box<dyn Outside + 'a>> would make Sessions<'a> invariant and break Delivery's &'a Sessions<'a>.

Evidence

  • Before: the Resume rules could only be tested by running the binary against fake claude/codex on PATH. Seven end-to-end tests in tests/background_work.rs.
    After: seven in-process tests in session::tests pass in about 0.01s:
    a clean session                      → one call, no Resume, its final message
    killed work + session id             → one Resume (implement-resume, id s1, the Resume prompt), "resuming it once"
    the Resume ends the same way         → no second Resume, "the Resume … carrying on", later error "implement session … and a later step failed: no PR found"
    no session id                        → no Resume, "the session … carrying on"
    a failing session                    → error unchanged, no Resume, no ending recorded
    steps that succeed after killed work → result unchanged
    two sessions leave killed work       → named oldest first, joined by "; "
    the last log                         → the Resume's path when there was a Resume
    
  • These four end-to-end tests are now covered in-process and were deleted: no-session-id, the Resume ending the same way, the later failure naming the work, and a Repair being resumed. The now-unused FAKE_CLAUDE_NO_SESSION_ID knob was removed from the fakes. The Claude Resume test, the merge refused after killed work, the session that ends in an error, and every Codex test are kept.
  • cargo clippy --all-targets is clean. The last two cargo test runs passed in full (1,053 tests). One earlier full run reported a single failure that did not happen again. Its name wasn't captured, and it was likely a timing-sensitive end-to-end test on a loaded machine.

Merge Danger

Door: two-way

The change is internal to src/session.rs, plus test deletions. Reverting it brings everything back.

Blast Radius: sessions

Every Run, Repair and Architect run session goes through the real adapter. It is the old spawn and follow code, moved but otherwise unchanged. The kept end-to-end tests exercise it on both Harnesses.

Unaddressed findings

Standards

  • Data clump (kind, resume, prompt, log): skipped. The four values travel together only across a private seam with a single method, and the issue specifies this signature.
  • Duplicated Call::Session construction in Scripted::run_session and the session() test helper: skipped. One builds from what was received and the other from what is expected. Sharing that code would hide what the test asserts.
  • The log.exists() filter applies in within but not in taking: skipped. The issue asks for exactly this ("The rules' own entry point … returns the last log unfiltered"), and taking's doc comment says so.

Spec

  • Repair Resume has no exact in-process twin: skipped. The rules don't depend on the kind, and the issue lists that end-to-end test for deletion.

Closes #407

🤖 Generated with Claude Code

JacobStephens2 and others added 2 commits October 5, 2026 02:44
… seam (#407)

The Resume rules reach the machine only through a private `Outside` seam:
run one session, and hand on a progress line. The real adapter spawns the
Harness's CLI as before; a scripted one drives the rules in-process. The
end-to-end tests those now cover go, with the fakes' no-session-id knob.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ecords no ending

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@JacobStephens2
JacobStephens2 merged commit b7c4535 into main Oct 5, 2026
13 checks passed
@JacobStephens2
JacobStephens2 deleted the issue-407 branch October 5, 2026 07:08
@JacobStephens2 JacobStephens2 mentioned this pull request Oct 5, 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.

The Sessions module owns its Resume rules, tested in-process behind a seam

1 participant