Repository navigation
fix(task-bridge): belt-suspenders guard against non-task .txt fallthrough (closes #1035) - #1036
Merged
Merged
Conversation
…ough (closes #1035) PR #1033 introduced `results/<channel-key>.task-{id}.txt` as a new filename namespace for the per-channel pull path used by discord-voice + phone. The existing consumers don't pick those up, except for one fragile spot: `src/task-bridge.ts`'s result-watcher has an unconditional fallthrough that, when voice-agent's client is connected, fires `onResult()` for any non-empty `.txt` file past the `voice-*` / dedup / offline-forward branches. Without a guard, a `<key>.task-foo.txt` file could race the per-channel scanner and *also* be injected into voice. PR #1033 accepted the race (scanner's read-and- delete usually wins); this guard closes it. Change: - Add `_shouldFallthrough(file)` predicate near `_isVoiceTask` — returns true only for `task-*` / `voice-*` prefixes. Rejects the new-namespace files, `proactive-*` (discord-bridge's poll_proactive handles those), and any unfamiliar prefix. - Gate the fallthrough at the result-watcher with `if (!_shouldFallthrough(file)) continue;` before the existing `if (result)` block. - Test: `tests/task-bridge-fallthrough-guard.test.ts` exercises the predicate across canonical-task / voice-push / channel-pull-namespace / proactive / unknown-prefix cases. Anchored to the start of the filename — `prefix-task-` and `xtask-` are correctly rejected. No behavioral change for the existing `task-*` / `voice-*` paths (both still fall through). `proactive-*` was previously hitting the fallthrough as a latent duplicate-delivery hazard (discord-bridge's `poll_proactive` also consumes it); the guard now skips it cleanly, matching its actual intended consumer. Verified: - `npm run typecheck` clean - 268 existing TS tests pass + 6 new tests pass - No other consumer modified Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The #1035 belt-suspenders guard rejected `proactive-*.txt`, framed as "closing a duplicate-delivery hazard." That framing was wrong — per the long-standing proactive-voice rule, `proactive-*` files are spoken by the voice agent when the client is connected. That delivery has no explicit handler upstream in startResultWatcher; the unconditional fallthrough IS the path. Blocking it silently disables voice-spoken proactive messages. Add `proactive-` to the `_shouldFallthrough` allowlist so the original delivery path survives. The guard still rejects `<channel-key>.task-*` (the actual #1035 target). Flip the proactive test case from "rejects" to "allows", with a docstring explaining why both consumers coexist. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
liususan091219
pushed a commit
to liususan091219/sutando-fork
that referenced
this pull request
May 28, 2026
…ough (closes sonichi#1035) (sonichi#1036) * fix(task-bridge): belt-suspenders guard against non-task .txt fallthrough (closes sonichi#1035) PR sonichi#1033 introduced `results/<channel-key>.task-{id}.txt` as a new filename namespace for the per-channel pull path used by discord-voice + phone. The existing consumers don't pick those up, except for one fragile spot: `src/task-bridge.ts`'s result-watcher has an unconditional fallthrough that, when voice-agent's client is connected, fires `onResult()` for any non-empty `.txt` file past the `voice-*` / dedup / offline-forward branches. Without a guard, a `<key>.task-foo.txt` file could race the per-channel scanner and *also* be injected into voice. PR sonichi#1033 accepted the race (scanner's read-and- delete usually wins); this guard closes it. Change: - Add `_shouldFallthrough(file)` predicate near `_isVoiceTask` — returns true only for `task-*` / `voice-*` prefixes. Rejects the new-namespace files, `proactive-*` (discord-bridge's poll_proactive handles those), and any unfamiliar prefix. - Gate the fallthrough at the result-watcher with `if (!_shouldFallthrough(file)) continue;` before the existing `if (result)` block. - Test: `tests/task-bridge-fallthrough-guard.test.ts` exercises the predicate across canonical-task / voice-push / channel-pull-namespace / proactive / unknown-prefix cases. Anchored to the start of the filename — `prefix-task-` and `xtask-` are correctly rejected. No behavioral change for the existing `task-*` / `voice-*` paths (both still fall through). `proactive-*` was previously hitting the fallthrough as a latent duplicate-delivery hazard (discord-bridge's `poll_proactive` also consumes it); the guard now skips it cleanly, matching its actual intended consumer. Verified: - `npm run typecheck` clean - 268 existing TS tests pass + 6 new tests pass - No other consumer modified Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(task-bridge): allow proactive-* through the fallthrough guard The sonichi#1035 belt-suspenders guard rejected `proactive-*.txt`, framed as "closing a duplicate-delivery hazard." That framing was wrong — per the long-standing proactive-voice rule, `proactive-*` files are spoken by the voice agent when the client is connected. That delivery has no explicit handler upstream in startResultWatcher; the unconditional fallthrough IS the path. Blocking it silently disables voice-spoken proactive messages. Add `proactive-` to the `_shouldFallthrough` allowlist so the original delivery path survives. The guard still rejects `<channel-key>.task-*` (the actual sonichi#1035 target). Flip the proactive test case from "rejects" to "allows", with a docstring explaining why both consumers coexist. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (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.
Closes #1035. Follow-up to PR #1033 (per-channel pull path for discord-voice + phone).
Background
PR #1033 introduced
results/<channel-key>.task-{id}.txtas a new filename namespace for the per-channel pull path used by discord-voice + phone. Per-consumer audit in #1033's PR body confirmed no existing consumer claims those filenames — except for one fragile spot Maddy flagged in review:src/task-bridge.ts's result-watcher has an unconditional fallthrough that, when voice-agent's client is connected, firesonResult()for any non-empty.txtfile past thevoice-*/ dedup / offline-forward branches. Without a guard, a<key>.task-foo.txtfile could race the per-channel scanner inside discord-voice / phone and also be injected into the live voice session. PR #1033's mitigation is the scanner's read-and-delete usually winning the race — but the race exists, and it was filed as out-of-scope for #1033's narrow design.Change
Two-part, ~5 LOC of behavior change plus a comment + helper + test:
src/task-bridge.ts— add_shouldFallthrough(file)predicate near_isVoiceTaskreturningtrueonly fortask-*/voice-*prefixes; gate the unconditional fallthrough withif (!_shouldFallthrough(file)) continue;immediately before the existingif (result)block.tests/task-bridge-fallthrough-guard.test.ts— new test covering canonical-task / voice-push / channel-pull-namespace / proactive / unknown-prefix cases. Anchored to the start of the filename (rejectsprefix-task-andxtask-).The predicate is exported (
_-prefixed convention, same as_isVoiceTask) because the watcher'ssetIntervalbody is awkward to exercise in isolation; testing the predicate directly is cleaner and locks in the contract.Behavioral impact
task-*.txtonResultvoice-*.txtproactive-*.txtpoll_proactivein discord-bridge also reads it)poll_proactive, which matches its design<channel-key>.task-*.txt(PR #1033 new namespace)onResultonResultif any tool ever drops it inresults/The
proactive-*change is a side-effect cleanup, not a regression:poll_proactivewas always the intended consumer, andtask-bridgereachingonResulton those files was the bug.Verification
npm run typecheckclean🤖 Generated with Claude Code