Skip to content

fix(agent-runner): bound hasIdenticalSend to the turn in flight#3014

Open
vishnujayvel wants to merge 1 commit into
nanocoai:mainfrom
vishnujayvel:fix/2997-recurring-reminder-identical-send
Open

fix(agent-runner): bound hasIdenticalSend to the turn in flight#3014
vishnujayvel wants to merge 1 commit into
nanocoai:mainfrom
vishnujayvel:fix/2997-recurring-reminder-identical-send

Conversation

@vishnujayvel

Copy link
Copy Markdown

Type of Change

  • Feature skill - adds a channel or integration (source code changes + SKILL.md)
  • Utility skill - adds a standalone tool (code files in .claude/skills/<name>/, no source changes)
  • Operational/container skill - adds a workflow or agent skill (SKILL.md only, no source changes)
  • Fix - bug fix or security fix to source code
  • Simplification - reduces or simplifies source code
  • Documentation - docs, README, or CONTRIBUTING changes only

Description

What: hasIdenticalSend (container/agent-runner/src/db/messages-out.ts) had no seq/time bound, so it scanned all of messages_out history. A recurring task with fixed reminder text delivers on its first fire, then every later fire's turn-final <message> block matches the previous fire's identical row and gets silently dropped as a stale-history "already sent" echo — zero delivery from the second fire onward.

Why: Task-fire sends are written with in_reply_to null by design, so yesterday's fire writes exactly the row that today's lookup matches. On a hit, sendToDestination returns before writing — no error, no log surfacing to the host (the only trace is a Dropping turn-final echo line in the container log, gone once the container exits with --rm).

How it works: Adds getMaxSeq() (extracted from the existing writeMessageOut logic) and threads a sinceSeq floor — captured once at the top of each processQuery turn — through dispatchResultTextsendToDestinationhasIdenticalSend. The lookup now only matches rows written during the current turn, so a recurring reminder never collides with its own previous fire. The original #2981 guard (dropping the echo of an MCP send_message made earlier in the same turn) still works, since that row lands above the floor.

Note: the issue's suggested fix pointed at reusing getToolSentTextsSince(floorSeq) / toolSentFloorSeq as an existing primitive for this — that function does not actually exist in the codebase (verified via grep -rn "getToolSentTextsSince\|toolSentFloorSeq"). The floor here (getMaxSeq() + sinceSeq) is built fresh, following the same shape.

How it was tested: See Test plan below — full suite, typecheck, and a revert-confirms-fail check on the new regression test.

Fixes #2997

Credit to @glifocat for the report, root-cause analysis, and a ready-to-run regression test in the issue body.

For Skills

  • SKILL.md contains instructions, not inline code (code goes in separate files)
  • SKILL.md is under 500 lines
  • I tested this skill on a fresh clone

Test plan

Repo: container/agent-runner (bun workspace).

$ bun install
107 packages installed [433.00ms]

$ bun run typecheck
$ tsc --noEmit
(exit 0, no output)

$ bun test
119 pass
0 fail
270 expect() calls
Ran 119 tests across 13 files. [4.15s]

$ bun test src/poll-loop.test.ts -t "2997"
2 pass
0 fail
Ran 2 tests across 1 file.

Revert-confirms-fail check (checked out src/db/messages-out.ts and src/poll-loop.ts from the parent commit, keeping the new tests in poll-loop.test.ts):

$ bun test src/poll-loop.test.ts -t "2997"
[poll-loop] Dropping turn-final echo of an already-sent task message to discord-test
error: expect(received).toHaveLength(expected)
Expected length: 2
Received length: 1
(fail) hasIdenticalSend seq floor (#2997) > delivers a recurring task reminder even when an identical row exists from a previous fire

TypeError: getMaxSeq is not a function.
(fail) hasIdenticalSend seq floor (#2997) > still drops the turn-final echo of an MCP send made within the same turn

0 pass
2 fail

Both new tests fail without the fix (the first reproduces the exact bug: 1 delivered row instead of 2, the reminder silently dropped) and pass with it. Restored the fix afterward and reran the full suite to confirm a clean tree (119 pass, 0 fail).

hasIdenticalSend had no seq/time bound, so it scanned all of
messages_out history. A recurring task's fixed-text reminder delivers
on its first fire, then every later fire's turn-final <message> block
matches yesterday's identical row and gets silently dropped as a
stale-history "already sent" echo — zero delivery from the second fire
onward.

Add a seq floor (getMaxSeq(), captured once at the top of each
processQuery turn) so the lookup only considers rows written during
the current turn. The original nanocoai#2981 guard (dropping the echo of an
MCP send_message made earlier in the same turn) still works, since
that row lands above the floor.

Closes nanocoai#2997
@github-actions github-actions Bot added follows-guidelines PR was created using the current contributing template PR: Fix Bug fix labels Jul 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

follows-guidelines PR was created using the current contributing template PR: Fix Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

hasIdenticalSend matches sends from previous fires, so recurring reminders with fixed text stop arriving

1 participant