🐛 fix(kanban): route transition wakes to the origin thread, not Home - #51
Merged
Merged
Conversation
A card born in a thread must report back to THAT thread, but the wake was dispatching to the Home channel with thread=None — so from the watcher's seat the pipeline went dark: work ran, cards moved, follow-ups were commissioned, and none of it surfaced. Root cause: the transition-wake destination is read verbatim off the notify subscription row, and a thread-born card ends up with TWO rows — the correct origin sub (chat+thread) stamped at filing, plus a later thread-less channel sub (e.g. a review-stage defensive re-subscribe to the Home channel). The emit-wake loop fires one wake per row, so the card double-fires: one to the origin thread (correct) and one to Home/thread=None (dark). Fix at both the write and the egress: - add_notify_sub skips a thread-less sub when the same (task, platform) already has a thread-bearing sub, so the dark-routing duplicate is never written. A genuinely channel-born card (no thread sub) is unaffected. - dedupe_wake_subs collapses a card's subs to a single wake target per (task, platform), preferring the thread-bearing sub, and the notifier gates the agent-wake claim on it — so even a legacy DB that already has both rows fires exactly one wake, to the origin thread. Distinct real threads are preserved; only the thread-less duplicate is dropped. The chat-ping path (separate cursor) is untouched. Restart-gated: touches gateway notifier/emit + kanban_db; verify against a restarted gateway. Filing-side origin resolution (literal 'origin' sentinel, review-stage bare-channel subscribe) lives in a separate repo and is tracked as a gated follow-up. Tests: new unit coverage for dedupe_wake_subs + the add_notify_sub guard, and an integration test proving a card with dup subs fires ONE wake to the thread (RED-verified: two wakes without the gate).
Owner
Author
|
No changes needed. The root cause holds up against the board: the wake destination is read verbatim off the notify-sub row, and a thread-born card ends up with two rows — the correct origin sub plus a later thread-less channel sub — so the emit loop fires two wakes, one of them dark to Home/thread=None. The fix closes it at both the write ( Verified rather than assumed:
Two things carry forward, both already accounted for:
|
cwest
marked this pull request as ready for review
July 6, 2026 21:41
cwest
added a commit
that referenced
this pull request
Jul 26, 2026
…51) A card born in a thread must report back to THAT thread, but the wake was dispatching to the Home channel with thread=None — so from the watcher's seat the pipeline went dark: work ran, cards moved, follow-ups were commissioned, and none of it surfaced. Root cause: the transition-wake destination is read verbatim off the notify subscription row, and a thread-born card ends up with TWO rows — the correct origin sub (chat+thread) stamped at filing, plus a later thread-less channel sub (e.g. a review-stage defensive re-subscribe to the Home channel). The emit-wake loop fires one wake per row, so the card double-fires: one to the origin thread (correct) and one to Home/thread=None (dark). Fix at both the write and the egress: - add_notify_sub skips a thread-less sub when the same (task, platform) already has a thread-bearing sub, so the dark-routing duplicate is never written. A genuinely channel-born card (no thread sub) is unaffected. - dedupe_wake_subs collapses a card's subs to a single wake target per (task, platform), preferring the thread-bearing sub, and the notifier gates the agent-wake claim on it — so even a legacy DB that already has both rows fires exactly one wake, to the origin thread. Distinct real threads are preserved; only the thread-less duplicate is dropped. The chat-ping path (separate cursor) is untouched. Restart-gated: touches gateway notifier/emit + kanban_db; verify against a restarted gateway. Filing-side origin resolution (literal 'origin' sentinel, review-stage bare-channel subscribe) lives in a separate repo and is tracked as a gated follow-up. Tests: new unit coverage for dedupe_wake_subs + the add_notify_sub guard, and an integration test proving a card with dup subs fires ONE wake to the thread (RED-verified: two wakes without the gate). (cherry picked from commit 6b294f3)
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.
Problem
A kanban card born in a thread must report back to THAT thread, but every
transition wake was dispatching to the Home channel with
thread=None. From thewatcher's seat the pipeline "went dark": work ran, cards moved, follow-ups were
commissioned — and none of it surfaced in the thread.
Verified against the live board: 43 correct thread subs vs 105
Home/thread-less subs; 15 cards carry BOTH a correct
discord:<chat>:<thread>sub and a later Home/thread-less sub. The transition-wake destination is read
verbatim off the notify subscription row, and the emit-wake loop fires one wake
per row — so a card with both rows double-fires: one to the origin thread
(correct) and one to Home/
thread=None(dark).Root cause
Two writers produce the thread-less Home row on top of a correct origin sub:
the notifier's Home-channel fallback and a review-stage defensive re-subscribe to
the bare Home channel. Once written, the extra row claims + fires its own wake.
Fix (both the write and the egress)
add_notify_subskips a thread-less sub when the same(task, platform)already has a thread-bearing sub — the dark-routing duplicate is never written.
A genuinely channel-born card (no thread sub anywhere) is unaffected.
dedupe_wake_subscollapses a card's subs to a single wake target per(task, platform), preferring the thread-bearing sub; the notifier gates theagent-wake claim on it, so even a legacy DB that already has both rows fires
exactly one wake — to the origin thread. Distinct real threads are preserved;
only the thread-less duplicate is dropped. The chat-ping path (separate cursor)
is untouched.
Tests
dedupe_wake_subs(order-independent, keeps distinct cards/threads, keepsa lone channel sub) + the
add_notify_subguard (skips thread-less-over-thread,allows a legitimate channel sub).
ONE wake, to the thread. RED-verified — two wakes without the gate
(
origin_chat_id= both the thread and Home), one with it.transition + thread-origin) and 279 passed / 1 skipped (kanban_tools +
kanban core).
Notes
kanban_db;verify against a restarted gateway, not a stale one. End-to-end acceptance
is a real
#researchthread request confirming the commissioning ping +completion synopsis land in the origin thread (log-confirmed), not a code-read.
literal
Report back to: originsentinel resolution at filing and thereview-stage bare-channel subscribe. This PR makes the wake robust regardless;
the follow-up fixes the source.
Base:
cwest/integration.