pollers: skip own posts case-insensitively, self handle from config - #59
Conversation
Every poller that feeds the wake pipeline must drop the agent's OWN posts, or each post the agent makes re-enters its notification file as a "new message" and burns a wake turn. On 2026-08-14 the MacBook room/DM poller was found shipping with NO self-filter at all in its DM path and a hardcoded one in its room path, and the filters that did exist compared handles case-SENSITIVELY. That masked the bug for months: GroupMind returns the handle's REGISTERED casing in message `from` fields (e.g. @claudemb) while configs usually hold lowercase (@claudemb), so the "filter" never matched and self-posts flowed straight back into the wake pipeline. Fix the construction, not just the instances: - New src/common/handles.mjs: handleKey() (trim, drop leading '@', lowercase), resolveSelfHandle() (IAK_SELF_HANDLE env override, then explicit CLI value, then poller.handle from config), isSelfSender(). - src/room-poller.mjs, src/adapters/groupmind.mjs, src/adapters/xfor.mjs, src/room-automation.mjs: use the shared helper instead of hand-rolled case-sensitive compares. - src/team-relay/room-poller.mjs: normalizeHandle() now lowercases, so the room self-skip, DM self-skip, and owner detection are all case-insensitive; self handle resolves through resolveSelfHandle(). - scripts/claudemb-poll.sh: self handle now sourced from poller.handle in config (IAK_SELF_HANDLE env wins) instead of hardcoded @claudemb; the DM loop gets the missing self-skip; the DM fetch URL uses the resolved handle instead of a hardcoded one (the DM endpoint's `to=` filter is case-insensitive, verified live); the resolved handle is exported as IAK_SELF_HANDLE so wake-on-mention.sh's self-mention skip actually engages. - scripts/claude-gui-poll.sh: comparison lowercased; IAK_SELF_HANDLE supported (legacy SELF_HANDLE kept). - scripts/room-poll-check.py: own-handle membership normalized case-insensitively; handle sourced from IAK_SELF_HANDLE (legacy IAK_SELF_HANDLES kept) or poller.handle in the machine config. - test/handles.test.mjs: covers the exact masked-bug case (@claudemb vs @claudemb), env-override precedence, and adapter shouldSkip behavior. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
|
Reviewed against the source and executed, not just read. Verified:
One caution before merge — the env override on shared machines:
The second is the one I'd take: it converts a silent failure into a visible one, which is this week's entire theme. Nothing blocking. Merge-ready with the log line. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f7b22a1a16
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| import { groupmindAdapter } from '../src/adapters/groupmind.mjs'; | ||
| import { xforAdapter } from '../src/adapters/xfor.mjs'; |
There was a problem hiding this comment.
Patch the adapters that the CLI actually loads
When ide-agent-kit platform watch or xfor poll/watch receives a self-authored item whose registered casing differs from the configured handle, these assertions pass only because they import src/adapters/*; bin/cli.mjs instead imports src/team-relay/adapters/*, where both shouldSkip implementations remain case-sensitive. Consequently, the shipped commands still queue, notify, and nudge on their own posts, so apply the helper to the team-relay adapters and exercise those exports in these tests.
Useful? React with 👍 / 👎.
| if (sender === selfHandle) continue; | ||
| // Skip own messages (case-insensitive; see src/common/handles.mjs) | ||
| const sender = m.user?.handle || m.from || m.sender || ''; | ||
| if (isSelfSender(sender, selfHandle)) continue; |
There was a problem hiding this comment.
Normalize the automation implementation loaded by the CLI
When ide-agent-kit automate posts an action and the API returns the sender using different casing, this new comparison is never executed because bin/cli.mjs imports src/team-relay/room-automation.mjs, whose self check remains case-sensitive. An automation rule can therefore consume its own output and repeatedly fire another action; apply the same helper to the team-relay implementation or redirect the CLI to this implementation.
Useful? React with 👍 / 👎.
ThinkOffApp
left a comment
There was a problem hiding this comment.
Adversarial review completed. I found no correctness issues in the changed self-handle filtering paths. The new shared normalization covers case and optional @ consistently, the DM self-skip is present in claudemb-poll.sh, and the targeted handle tests (9/9), bash -n, and py_compile pass. A fresh checkout's full npm test is not independently reproducible without installing the local workspace dependencies; after dependency installation, please rerun the advertised 220-test suite before merging.
Problem
Every poller that feeds an agent's wake pipeline must drop the agent's OWN posts, or each post the agent makes re-enters the new-messages file as a "new message" and burns a wake turn (an echo loop of self-wakes).
On 2026-08-14 the MacBook room/DM poller was found shipping with no self-filter at all in its DM path, a hardcoded one in its room path, and its stop-hook text even claimed "your own posts are filtered out by the poller", which was false. Worse, the filters that did exist across the kit compared handles case-sensitively, which masked the bug: GroupMind returns the handle's REGISTERED casing in message
fromfields (e.g.@claudeMB) while configs usually hold lowercase (@claudemb), so a===filter never matched. Live proof: posting via the MCP server with config handle@claudembreturns"from": "@claudeMB".Fix (construction, not instances)
src/common/handles.mjsshared by all Node pollers:handleKey()— trim, drop leading@, lowercaseresolveSelfHandle()—IAK_SELF_HANDLEenv override → explicit CLI value →poller.handlefrom the agent's configisSelfSender()— case- and@-insensitive comparison, never matches an empty handlesrc/room-poller.mjs,src/adapters/groupmind.mjs,src/adapters/xfor.mjs,src/room-automation.mjs— replace hand-rolled case-sensitive compares with the shared helper.src/team-relay/room-poller.mjs—normalizeHandle()now lowercases, making the room self-skip, DM self-skip, and owner detection case-insensitive; self handle resolves throughresolveSelfHandle().scripts/claudemb-poll.sh— self handle sourced frompoller.handlein config (IAK_SELF_HANDLEenv wins) instead of hardcoded; the DM loop gains the missing self-skip; the DM fetch URL uses the resolved handle (the DM endpoint'sto=filter is case-insensitive, verified against the live API); the resolved handle is exported asIAK_SELF_HANDLEsowake-on-mention.sh's self-mention skip actually engages (it previously defaulted to empty).scripts/claude-gui-poll.sh— comparison lowercased;IAK_SELF_HANDLEsupported, legacySELF_HANDLEkept.scripts/room-poll-check.py— own-handle membership normalized case-insensitively; handle sourced fromIAK_SELF_HANDLE(legacyIAK_SELF_HANDLEScomma list kept) orpoller.handlein the machine config, so new-user defaults come from config rather than a hardcoded handle.Tests
test/handles.test.mjs(9 tests) covering the exact masked-bug case (@claudeMBvs@claudemb), env-override precedence, empty-handle safety, and adaptershouldSkipbehavior.bash -n/py_compileclean;room-poll-check.pyexercised withIAK_SELF_HANDLEoverride and withconfig/grok.example.json(poller.handle→@grokpicked up).🤖 Generated with Claude Code