feat(timeboxing): one path builds every session surface — root header + threaded card - #244
Merged
Merged
Conversation
…sions Approved design from the 2026-08-31 date-card clobber incident: extract the handoff branch's root+thread surface block into one function, have the slash fresh start delegate to it, and delete the aliased one-message branch. The PR #242 guard stays for legacy in-flight cards. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…d threaded under it The would-alias fresh start now delegates to _begin_timeboxing_session_surface: root header (the repurposed origin ack) plus the card as its first thread reply. The one-message layout that let a relabel erase the Stage-0 card is no longer reachable on the harness path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…h the one function Same surface, one implementation. The inline timeboxing arms of the redirect block are gone; the generic agent redirect stays as it was. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The delegation call to _begin_timeboxing_session_surface sat as a sibling of the try block instead of its first statement, so a failure inside the surface builder raised past route_slack_event instead of falling through to the existing in-thread fallback. Moves it back inside try, matching the brief and pre-refactor behavior. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A root whose card never arrived used to sit there pending forever, which is the same silence the incident produced by other means. Now it says so. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The guard found its call site with rindex, so it asserted on whichever _run_adaptive_timebox_turn came last in the source. When the handoff path moved into _begin_timeboxing_session_surface, last became the direct route's call and the guard failed on a key that was correct for that path. It now checks both sites by name: the surface builder keys off the redirected thread, the direct route off its own recipient. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… the whole surface The extracted surface builder gated the card's DM delivery on `not is_dm`, so a handoff that began in a DM opened the session correctly and then left the DM showing a spinner forever — no card, no controls, no link. The same dead end the convergence exists to remove, on the other door. The canceled-root guard also opened four statements after the root was posted, so a refused focus binding or a dead origin ack left an orphan pending root behind. It now covers everything the root's existence outlives. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
hugolytics
changed the base branch from
fix/timebox-date-reselect-keeps-card
to
main
September 1, 2026 20:55
hugolytics
pushed a commit
that referenced
this pull request
Sep 3, 2026
The working copy of these two was the pre-refactor version: 451 deletions against 174 insertions when diffed against origin/main, missing the session-surface work and the AST-based handoff test that landed in #244. They looked like work in progress and were not. Local main sat several merges behind while #242, #244 and #246 landed, so 41 of the 43 files that read as modified were byte-identical to origin — dirty against a stale HEAD, not against the branch. These two were the only real difference, and they were behind rather than ahead. Checked before restoring rather than after: `_post_pending_harness_approval`, `PLANNING_MODEL` and the approve-commits path appear identically in both copies, and the session-id, `committed_tx_id` and `NEEDS_APPROVAL` fixes are already on origin in `harness_bridge.py` and `dsh_commit_gate_hook.py`. The working copy held nothing unique. Both files are backed up with a patch against origin in case that judgement was wrong. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements
docs/superpowers/specs/2026-09-01-timebox-session-surface-convergence-design.md.Stacked on #242 — merge that first; this PR retargets to
mainautomatically when it does. #242's reselect guard is deliberately kept here, now scoped to legacy in-flight cards.Why
On 2026-08-31 22:57 a live
/timeboxsession posted its Stage-0 date card, the user picked a different day from the dropdown, and the card lost every button — the session then waited forever for a Confirm click on a control that no longer existed. Root cause: in the slash route one Slack message served as ack, progress card, outcome card and session-thread root, so a text-only "relabel the thread root" update wiped the card's blocks.#242 guarded the one writer that did it. This removes the layout that armed it.
What changed
_begin_timeboxing_session_surface(nested inroute_slack_event) is now the single builder of every timeboxing session surface: a dedicated root header plus the working/card message threaded under it. Both doors call it — the slash/direct fresh start and the receptionist handoff — so the two paths can no longer drift apart. The root is only ever a header, so a relabel cannot erase a control again.A surface that half-builds relabels its root
canceledrather than leaving a dead header that looks live.Two bugs the reviews caught, both fixed here
not is_dm, so a session started in a DM opened correctly in the channel and left the DM showing a spinner forever — the same dead end this branch exists to remove, on the other door. Verified by probe against pre-refactor code, fixed, and covered by a test.test_both_entry_points_reach_the_harnesscounted source lines; a total regression of the handoff to AutoGen left it green. Rewritten AST-based so it fails loudly. A second guard (test_the_handoff_interception_uses_the_redirected_thread) broke mid-branch for the same class of reason — a positionalrindexprobe that followed the wrong call site after the refactor — and is likewise now AST-based.Tests
111 passing across the timeboxing, routing, replay and handoff suites. Every new test was broken on purpose and observed failing before being trusted (CLAUDE.md rule); assertions run over system-minted identifiers only — Slack
ts, action ids, channel ids — never prose.Two pre-existing order-dependent failures in very large full-suite runs (
test_slack_handoff_flow/test_slack_tasks_handoff_flow::test_slack_handoff_sets_focus_and_forwards*) were confirmed failing identically at the branch base; they pass in isolation and are untouched here.Follow-up filed
#243 — text-only root relabels strip the root's constraint Review buttons. Pre-existing on the handoff route; this branch generalises the exposure, so it is recorded rather than widened into this diff.
🤖 Generated with Claude Code