Skip to content

pollers: skip own posts case-insensitively, self handle from config - #59

Merged
ThinkOffApp merged 1 commit into
mainfrom
fix/poller-self-filter
Aug 15, 2026
Merged

pollers: skip own posts case-insensitively, self handle from config#59
ThinkOffApp merged 1 commit into
mainfrom
fix/poller-self-filter

Conversation

@ThinkOffApp

Copy link
Copy Markdown
Owner

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 from fields (e.g. @claudeMB) while configs usually hold lowercase (@claudemb), so a === filter never matched. Live proof: posting via the MCP server with config handle @claudemb returns "from": "@claudeMB".

Fix (construction, not instances)

  • New src/common/handles.mjs shared by all Node pollers:
    • handleKey() — trim, drop leading @, lowercase
    • resolveSelfHandle()IAK_SELF_HANDLE env override → explicit CLI value → poller.handle from the agent's config
    • isSelfSender() — case- and @-insensitive comparison, never matches an empty handle
  • src/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.mjsnormalizeHandle() now lowercases, making the room self-skip, DM self-skip, and owner detection case-insensitive; self handle resolves through resolveSelfHandle().
  • scripts/claudemb-poll.sh — self handle sourced from poller.handle in config (IAK_SELF_HANDLE env wins) instead of hardcoded; the DM loop gains the missing self-skip; the DM fetch URL uses the resolved handle (the DM endpoint's to= filter is case-insensitive, verified against the live API); the resolved handle is exported as IAK_SELF_HANDLE so wake-on-mention.sh's self-mention skip actually engages (it previously defaulted to empty).
  • 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 comma list kept) or poller.handle in the machine config, so new-user defaults come from config rather than a hardcoded handle.
  • README — env table updated.

Tests

  • New test/handles.test.mjs (9 tests) covering the exact masked-bug case (@claudeMB vs @claudemb), env-override precedence, empty-handle safety, and adapter shouldSkip behavior.
  • Full suite: 220/220 pass.
  • bash -n / py_compile clean; room-poll-check.py exercised with IAK_SELF_HANDLE override and with config/grok.example.json (poller.handle@grok picked up).

🤖 Generated with Claude Code

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>
@cursor

cursor Bot commented Aug 15, 2026

Copy link
Copy Markdown

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.

@ThinkOffApp
ThinkOffApp merged commit 3b92b90 into main Aug 15, 2026
3 checks passed
@ThinkOffApp

Copy link
Copy Markdown
Owner Author

Reviewed against the source and executed, not just read.

Verified:

  • The helper is right: handleKey trims, strips only a leading @ (anchored — the old .replace('@','') removed the first @ anywhere), lowercases; isSelfSender guards the empty key so a missing config can never filter everybody.
  • All four pollers + room-automation + both adapters converted to the one helper — no surviving hand-rolled comparison (grepped).
  • The DM path really is included.
  • New tests: 9/9 pass on the branch. (The full suite fails in a bare worktree on ERR_MODULE_NOT_FOUND: user-intent-kit — that's my empty node_modules, environmental, not this PR.)
  • The bug was real and the mechanism is well documented in the header comment: registered casing in from vs lowercase configs, so every self-post re-entered the wake pipeline.

One caution before merge — the env override on shared machines: IAK_SELF_HANDLE wins over config, and the MacBook runs two agents (claudeMB + codexmb). If that variable ever lands in a shared shell profile instead of a per-daemon environment, both pollers resolve the same self handle: one agent stops filtering itself (the self-wake loop this PR fixes comes straight back) and starts filtering its sibling (silently dropping real messages — worse than the original bug). Two cheap guards, either is enough:

  1. A doc line: per-process only, never in a profile.
  2. Log the resolved self-handle once at poller start — a misconfig becomes visible in the first line of output instead of a silence.

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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment thread test/handles.test.mjs
Comment on lines +6 to +7
import { groupmindAdapter } from '../src/adapters/groupmind.mjs';
import { xforAdapter } from '../src/adapters/xfor.mjs';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge 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 👍 / 👎.

Comment thread src/room-automation.mjs
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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge 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 ThinkOffApp left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

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.

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.

1 participant