Skip to content

Fix duplicate Discord DMs + phone transcript dedup - #43

Merged
sonichi merged 3 commits into
mainfrom
fix/dedup-discord-and-phone
Mar 30, 2026
Merged

sonichi merged 3 commits into
mainfrom
fix/dedup-discord-and-phone

Conversation

@liususan091219

Copy link
Copy Markdown
Collaborator

Summary

  • Discord bridge: deduplicate messages using message.id — gateway replays on reconnect were creating duplicate task files and sending duplicate DM replies
  • Phone server: fix duplicate transcript lines across Gemini reconnects (track processed index + dedup against last entry)
  • Phone server: cache task results per call session so repeated requests get instant replay instead of new round-trip

Test plan

  • Send several Discord DMs in quick succession — should get 1 reply each, not duplicates
  • Make a phone call, trigger a Gemini reconnect — transcript should not have doubled lines
  • On a phone call, ask the same question twice — second answer should be instant (cached)

🤖 Generated with Claude Code

liususan091219 and others added 3 commits March 30, 2026 09:51
On Gemini reconnect, conversationContext.items could reset or
re-populate history, causing turn.end to re-process already-transcribed
items. Now tracks a lastProcessedIdx that resets on context shrink, plus
deduplicates against the last recorded transcript entry.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
If the caller repeats a request (e.g. Gemini reconnected and they ask
again), return the cached result instantly instead of creating a new
task and making them wait. Cache is per-call session.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Discord gateway can replay events on reconnect, creating duplicate task
files and duplicate DM replies. Now tracks seen message IDs to skip
already-processed messages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@sonichi sonichi left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — task dedup via cache for phone calls, message ID dedup for Discord DMs, transcript dedup on reconnect. All needed fixes.

@sonichi
sonichi merged commit 94e7cd1 into main Mar 30, 2026
@sonichi
sonichi deleted the fix/dedup-discord-and-phone branch March 30, 2026 23:02
liususan091219 added a commit that referenced this pull request Apr 1, 2026
The same PR #43 commit also dropped {attachment_note} from the task
text, so Discord messages with file attachments lost the attachment
reference.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
liususan091219 added a commit that referenced this pull request Apr 1, 2026
* Fix watcher reliability: replace fswatch with persistent polling

fswatch -1 exits after one event and dies silently, causing tasks
to pile up for hours. Replace with a 2s polling loop that never
exits — catches tasks within 2 seconds regardless of fswatch state.

Fixes 8-minute phone task delays and missed Discord messages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix Discord bridge NameError: msg_text → text

The bridge received DMs but silently crashed when writing task files
because line 273 referenced `msg_text` (undefined) instead of `text`.
Every Discord DM task was lost with no error logged.

Also improved health check to detect stale bridge logs (>5min).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Restore attachment_note in Discord task file

The same PR #43 commit also dropped {attachment_note} from the task
text, so Discord messages with file attachments lost the attachment
reference.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Revert watcher to fswatch + add mkdir -p safety

The polling watcher broke task-notifications (never exits = never
notifies). Restore main's fswatch version which exits on detection.
Add mkdir -p for tasks/ and results/calls/ at startup.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Add tests/ to .gitignore for local test creation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Remove health check change — covered by PR #71

PR #71 has a more comprehensive health check fix (heartbeat files,
multi-process detection, stale log check). Remove overlap from this PR.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
sonichi added a commit that referenced this pull request Apr 20, 2026
…ion) (#489)

CodeQL alert #43 (js/resource-exhaustion, warning): the `ttl_ms` query
parameter on /mute-state?state=seeing flowed directly into setTimeout's
duration. Existing guard only checked `isFinite && > 0`, not an upper
bound — an unbounded ttl_ms would schedule a long-lived timer per
request (cheap-to-allocate DoS).

Fix: clamp with Math.min(ttl, 60000). The "seeing" overlay is UI flash
feedback (1.5-3s typical from `screen-capture-server.py`); nothing
legitimate needs a minute-plus timer. Default still 3000ms.

Closes the CodeQL queue: 15 alerts across 4 PRs (#485, #486, #487,
#488, this PR #489).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
liususan091219 added a commit that referenced this pull request Apr 21, 2026
…-logging) (#493)

## #43 js/resource-exhaustion (src/web-client.ts:2549)

#489's `Math.min(ttl, MAX_TTL_MS)` is an `isNumericFlowStep` (numeric
passthrough) in `codeql/javascript-all@2.6.27` — it narrows the value but
doesn't register as a sanitizer. The `js/resource-exhaustion` config only
blocks taint via `UpperBoundsCheckSanitizerGuard`, which matches
`RelationalComparison` AST nodes (`<`, `<=`, `>`, `>=`).

Rewrote the clamp as `ttl <= MAX_TTL_MS ? ttl : MAX_TTL_MS` — semantically
identical, but CodeQL now sees `ttl` as the lesser operand in a `<=`
comparison and blocks taint in the true branch.

## #44 js/clear-text-logging (src/voice-agent.ts:71)

The `${value.length}` and `${prefixOk}` interpolations in `console.error`
both read properties of `value`. The env var `GEMINI_API_KEY` triggers
`HeuristicNames` → tainted source; `allowImplicitRead` in the cleartext
config pulls any property of a tainted value into the logger sink.
#486 stopped echoing substrings but the length + ternary-prefix diagnostic
still flow the taint.

Dropped those two interpolations from the log; kept the name, expected
format ("AIza..." 35-60 chars), and remediation URL. Diagnostic loss is
minor — a malformed key always needs to be regenerated anyway.

## Verification

Local CodeQL CLI 2.25.2 + codeql/javascript-queries@2.3.7:
- main (dc7bfbe):  2 alerts (resource-exhaustion @ web-client.ts:2549, clear-text-logging @ voice-agent.ts:71)
- this branch:     0 alerts

`npx tsc --noEmit` clean.

Closes #43, #44. Together with #492 (merged earlier this session), this
zeroes the CodeQL security-code-scanning queue.

Co-authored-by: Xueqing Liu <xueqingliu@Xueqings-Mac-Studio.local>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
liususan091219 added a commit to liususan091219/sutando-fork that referenced this pull request May 28, 2026
* Phone server: fix duplicate transcript messages across reconnects

On Gemini reconnect, conversationContext.items could reset or
re-populate history, causing turn.end to re-process already-transcribed
items. Now tracks a lastProcessedIdx that resets on context shrink, plus
deduplicates against the last recorded transcript entry.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Phone server: cache task results to avoid duplicate work

If the caller repeats a request (e.g. Gemini reconnected and they ask
again), return the cached result instantly instead of creating a new
task and making them wait. Cache is per-call session.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Discord bridge: deduplicate messages using message.id

Discord gateway can replay events on reconnect, creating duplicate task
files and duplicate DM replies. Now tracks seen message IDs to skip
already-processed messages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
liususan091219 added a commit to liususan091219/sutando-fork that referenced this pull request May 28, 2026
…ichi#72)

* Fix watcher reliability: replace fswatch with persistent polling

fswatch -1 exits after one event and dies silently, causing tasks
to pile up for hours. Replace with a 2s polling loop that never
exits — catches tasks within 2 seconds regardless of fswatch state.

Fixes 8-minute phone task delays and missed Discord messages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix Discord bridge NameError: msg_text → text

The bridge received DMs but silently crashed when writing task files
because line 273 referenced `msg_text` (undefined) instead of `text`.
Every Discord DM task was lost with no error logged.

Also improved health check to detect stale bridge logs (>5min).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Restore attachment_note in Discord task file

The same PR sonichi#43 commit also dropped {attachment_note} from the task
text, so Discord messages with file attachments lost the attachment
reference.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Revert watcher to fswatch + add mkdir -p safety

The polling watcher broke task-notifications (never exits = never
notifies). Restore main's fswatch version which exits on detection.
Add mkdir -p for tasks/ and results/calls/ at startup.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Add tests/ to .gitignore for local test creation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Remove health check change — covered by PR sonichi#71

PR sonichi#71 has a more comprehensive health check fix (heartbeat files,
multi-process detection, stale log check). Remove overlap from this PR.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
liususan091219 pushed a commit to liususan091219/sutando-fork that referenced this pull request May 28, 2026
…exhaustion) (sonichi#489)

CodeQL alert sonichi#43 (js/resource-exhaustion, warning): the `ttl_ms` query
parameter on /mute-state?state=seeing flowed directly into setTimeout's
duration. Existing guard only checked `isFinite && > 0`, not an upper
bound — an unbounded ttl_ms would schedule a long-lived timer per
request (cheap-to-allocate DoS).

Fix: clamp with Math.min(ttl, 60000). The "seeing" overlay is UI flash
feedback (1.5-3s typical from `screen-capture-server.py`); nothing
legitimate needs a minute-plus timer. Default still 3000ms.

Closes the CodeQL queue: 15 alerts across 4 PRs (sonichi#485, sonichi#486, sonichi#487,
sonichi#488, this PR sonichi#489).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
liususan091219 added a commit to liususan091219/sutando-fork that referenced this pull request May 28, 2026
… (clear-text-logging) (sonichi#493)

## sonichi#43 js/resource-exhaustion (src/web-client.ts:2549)

sonichi#489's `Math.min(ttl, MAX_TTL_MS)` is an `isNumericFlowStep` (numeric
passthrough) in `codeql/javascript-all@2.6.27` — it narrows the value but
doesn't register as a sanitizer. The `js/resource-exhaustion` config only
blocks taint via `UpperBoundsCheckSanitizerGuard`, which matches
`RelationalComparison` AST nodes (`<`, `<=`, `>`, `>=`).

Rewrote the clamp as `ttl <= MAX_TTL_MS ? ttl : MAX_TTL_MS` — semantically
identical, but CodeQL now sees `ttl` as the lesser operand in a `<=`
comparison and blocks taint in the true branch.

## sonichi#44 js/clear-text-logging (src/voice-agent.ts:71)

The `${value.length}` and `${prefixOk}` interpolations in `console.error`
both read properties of `value`. The env var `GEMINI_API_KEY` triggers
`HeuristicNames` → tainted source; `allowImplicitRead` in the cleartext
config pulls any property of a tainted value into the logger sink.
sonichi#486 stopped echoing substrings but the length + ternary-prefix diagnostic
still flow the taint.

Dropped those two interpolations from the log; kept the name, expected
format ("AIza..." 35-60 chars), and remediation URL. Diagnostic loss is
minor — a malformed key always needs to be regenerated anyway.

## Verification

Local CodeQL CLI 2.25.2 + codeql/javascript-queries@2.3.7:
- main (dc7bfbe):  2 alerts (resource-exhaustion @ web-client.ts:2549, clear-text-logging @ voice-agent.ts:71)
- this branch:     0 alerts

`npx tsc --noEmit` clean.

Closes sonichi#43, sonichi#44. Together with sonichi#492 (merged earlier this session), this
zeroes the CodeQL security-code-scanning queue.

Co-authored-by: Xueqing Liu <xueqingliu@Xueqings-Mac-Studio.local>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

2 participants