Review: Hook + MCP fix: GroupMe send_message requires name-based resolution + user approval
Verdict: NEEDS_REFINEMENT
Template Completeness
- [x] Lineage
- [x] Repo
- [x] User Story
- [x] Context
- [x] File Targets
- [x] Acceptance Criteria
- [x] Test Expectations
- [x] Constraints
- [x] Checklist
- [x] Related
- [x] Type (extra section, fine)
All required template sections present. Well-written incident context with root cause analysis.
File Targets
- [x]
hooks/block-groupme-send.sh— NEW file. Reference patternhooks/block-mcp-merge.shconfirmed at/home/ldraney/claude-custom/hooks/block-mcp-merge.sh(19 lines, uses jq + permissionDecision: "ask"). Pattern is solid and appropriate. - [x]
settings.json— Confirmed at/home/ldraney/claude-custom/settings.json. PreToolUse section exists (lines 50-163) with clear pattern for adding new matcher entries. No existing GroupMe matcher present. - [x]
src/groupme_mcp/tools/messages.py— Confirmed at/home/ldraney/groupme-mcp/src/groupme_mcp/tools/messages.py. Currently acceptsgroup_id: strparameter (line 14).send_messagecallsget_client().send_message(group_id=group_id, text=text). - [x]
list_groups()function confirmed insrc/groupme_mcp/tools/groups.py(line 29) — callsget_client().list_groups()with no arguments. Available for live resolution. - [x] Tests directory confirmed:
tests/test_messages.py,tests/test_groups.py,tests/test_members.pyexist. - [ ]
add_memberandremove_member— ISSUE: Both also take rawgroup_id(confirmed inmembers.pylines 14 and 51). The hook covers all three tools, but the MCP interface fix in File Targets only mentionsmessages.py. Same stale-ID vulnerability exists for member operations. See Blast Radius.
Repo Placement
OK. Two repos correctly identified:
forgejo_admin/claude-custom— hook + settings.json (confirmed on Forgejo)forgejo_admin/groupme-mcp— tool interface change (confirmed on Forgejo)
Forgejo issue is filed on
claude-custom. Since work spans two repos, the checklist correctly calls for two PRs. The groupme-sdk exclusion is correct — SDK keeps raw group_id, MCP resolves above it.Note: The Forgejo issue lives on
claude-custom only. A second Forgejo issue on groupme-mcp would improve traceability for the MCP-side PR, but is not strictly required since the checklist already calls out both PRs.Dependencies
- No blocking dependencies found on the board. Board item #357 is in
todocolumn with nodepends:label. - Board item #98 (Context intelligence) is
in_progress— no conflict. - The
groupme-sdkpackage must already be published to Forgejo PyPI forgroupme-mcpto importlist_groupsfrom it. The ticket's Constraints section notes "Forgejo PyPI for groupme-sdk dependency" — this is documented. - No undocumented cross-repo dependencies found.
Acceptance Criteria
- [x] "send_message accepts group_name, not raw group_id" — Testable, clear.
- [x] "Ambiguous or missing group name returns helpful error" — Testable.
- [x] "PreToolUse hook fires on every send_message, add_member, remove_member" — Testable via hook test.
- [x] "Hook shows group name and message text in permission prompt" — Testable, but note:
add_member/remove_memberdon't have atextfield. Hook extraction logic needs to handle different tool_input shapes. - [ ] "Hook works under --dangerously-skip-permissions" — ISSUE: This criterion is misleading. Claude Code hooks ALWAYS run regardless of permission flags —
skipDangerousModePermissionPromptonly skips the dangerous-mode confirmation, not hooks. The criterion is technically true by default, but suggests the agent needs to verify something special. Recommend rewording to: "Hook fires even whenskipDangerousModePermissionPromptis true in settings.json" or removing it (it's inherent to hook semantics). - [x] "Forked/resumed sessions cannot bypass the gate" — True by design (hooks are settings.json-level, not session-level).
Blast Radius
- add_member and remove_member also take raw group_id. The hook gates all three (good), but the MCP interface fix only targets
send_message. If the goal is to eliminate stale-ID risk,add_member,remove_member, andlist_membersshould also resolve by name. An agent could still calladd_member(group_id="113983384")with a wrong ID — the hook would prompt, but the prompt would show a raw ID instead of a name, defeating the "name-based resolution" goal. - get_group also takes raw group_id (line 43 of groups.py) — lower risk since it's read-only, but inconsistent if send_message moves to name-based.
- Hook tool_input extraction must handle multiple shapes:
send_messagehasgroup_name+text;add_memberhasgroup_name+nickname+ contact fields;remove_memberhasgroup_name+membership_id. The ticket's hook description only mentions extractinggroup_nameandtext. - Existing tests will break.
tests/test_messages.pycallssend_message(group_id="12345", text=...)in 3 tests. These need updating as part of the MCP change.
Recommendation
Two issues to address before READY:
- Extend name-based resolution to add_member and remove_member (not just send_message). Add to File Targets:
members.py— MODIFY to acceptgroup_nameinstead ofgroup_id. Alternatively, scope this as a deliberate Phase 2 follow-up and document the decision in the ticket. - Update hook description to note it must handle different tool_input shapes (not all tools have a
textfield). The hook should extractgroup_namefrom all three tools, and show context-appropriate details (text for messages, nickname for add_member, membership_id for remove_member).
Minor nit: Reword or remove the "Hook works under --dangerously-skip-permissions" acceptance criterion — it's inherently true for all hooks and may confuse the implementing agent.