evi-auto-react
Use when every inbound chat message must be acknowledged instantly and model-independently — a two-stage read-receipt (hook fixed emoji now, model contextual emoji later).
SKILL.mdskills/auto-react/SKILL.md
GitHubAuto-react
Every inbound message gets an acknowledgment that does not depend on the model remembering to react. The acknowledgment is two-stage: the harness guarantees instant receipt, the model later upgrades it to something that fits the message.
What it guarantees
The owner always sees that the message landed — within milliseconds, before the model even starts a turn — and, by the end of the turn, sees a reaction that actually reflects the content. Instant receipt is a harness promise; contextual nuance is a model touch. Neither blocks the other.
How it works
Stage 1 — instant fixed (hook). A UserPromptSubmit hook extracts
chat_id/message_id from the <channel source="..."> tag and fires a
fire-and-forget setMessageReaction with a fixed emoji (👀). It runs before the
model's turn, so the receipt is model-independent: it lands even when context is
full or the model is mid-task. Failure is swallowed — a missing reaction must
never delay the turn.
Stage 2 — model upgrade. When the model processes the turn and understands the message, it overwrites the placeholder with a contextually right emoji (e.g. 🎉 for good news, 🔧 for a bug report) via the channel's react tool. The fixed 👀 is the floor; the model's choice is the finish.
Role split: immediacy is the hook, judgment is the model. The hook can react instantly but cannot know what the message means; the model knows what it means but cannot be trusted to react every single time. Stacking them gives both.
Implement in your project
Reference implementation: ../../scripts/hooks/auto-react.sh
Wire it on UserPromptSubmit (see
../../.claude/settings.json):
"UserPromptSubmit": [
{
"matcher": "",
"hooks": [
{ "type": "command", "command": "bash $CLAUDE_PROJECT_DIR/scripts/hooks/auto-react.sh", "timeout": 10 }
]
}
]
For stage 2, add a line to your agent instructions: "after understanding an inbound message, replace the 👀 placeholder with an emoji that fits, using the channel react tool." No code needed — it is a model behavior, the hook is the safety net under it.
The bot token is read from the channel plugin's own .env
($HOME/.claude/channels/telegram/.env), never from the project .env and
never into model context.
Adaptation points
- Different channel: replace the regex that pulls
chat_id/message_idand thecurlcall with your channel's reaction API. Keep the two stages. - No reaction API on your channel: stage 1 can post a lightweight ack instead; if even that is impossible, drop stage 1 and keep stage 2 only — but you lose the model-independent guarantee.
- Different placeholder: change the fixed emoji in the
curlbody; keep it distinct so stage 2 can tell "not yet upgraded" from "already contextual".
Verify
bash -n scripts/hooks/auto-react.shexits 0.- Send a test message → 👀 appears within a second (stage 1).
- Let the model finish the turn → the reaction changes to a content-fit emoji (stage 2).
Rollback
Remove the UserPromptSubmit entry from .claude/settings.json.