[Bug]: An agent's message to another thread runs any skill it mentions, as if the user typed it#24667

opened21d agobyOoffchan42

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

apps/server

Steps to reproduce

  1. From any thread, call the MCP tool t3_thread_send to a Claude thread with:
    text
    1PREFIX: the literal mention $technical-writing appears mid-sentence here. SUFFIX: report the exact text you received, then stop.
  2. Send the same text to a Codex thread.
  3. Send each thread a message that starts with the skill as a slash command instead:
    text
    1/technical-writing PREFIX: report the exact text you received, then stop.

Any installed skill works; we used technical-writing because it changes nothing.

Expected behavior

Text that one agent sends another arrives as text. A skill mention in it does not invoke the skill, because the recipient's human never asked for that. An agent that means to invoke a skill in another thread says so explicitly.

Actual behavior

Message from an agentClaude threadCodex thread
$technical-writing mid-sentencethe skill's full SKILL.md is added to the turn, framed as a /technical-writing command the user ranthe skill's full SKILL.md is added to the turn in a skill wrapper
Starts with /technical-writingsame as abovenothing added

The message text itself is kept; nothing is replaced. In the Claude thread the mention is rewritten to /technical-writing, and the command arguments hold only the text after it, so they drop everything before the mention. The recipient agent then reads that its user ran the skill on those arguments. Nothing tells it that another agent wrote the mention.

Source at eabcfa2 (main) matches this:

  • OrchestratorMcpService.ts records createdBy: agent and senderThreadId for the message, then passes its text on unchanged.
  • ClaudeSkillDispatch.ts (planClaudeSkillDispatch) turns any known $name mention into a native slash command, and ClaudeAdapterV2.ts sends it as user-role content, on both the start-turn and steer paths.
  • CodexAdapterV2.ts sends the text as plain input, and Codex invokes the mentioned skill itself.
  • Neither path uses the agent origin. scheduledTasks/webhookTemplate.ts already neutralizes mention and command syntax in untrusted webhook values, which looks like the boundary this case needs too.

Impact

Major degradation or frequent failure

Many skills treat an explicit invocation as the user's authorization to act. When agents message each other, as a coordinator relaying decisions or a reviewer reporting, a message that only names a skill (for example "the release skill reported X") starts that skill in the recipient. The recipient sees a command it cannot tell from its own user's. We saw it only with a read-only skill, and no tool ran.

Version or commit

T3 Code 0.1.3-nightly.20261024.3643; source read at eabcfa2 (2026-10-25)

Environment

Windows 11 x64; Claude Code (Claude Opus 5.5) and Codex (gpt-6-astra) threads

Workaround

Senders avoid writing skills as $name or a leading /name in messages to other threads.

Synced from GitHub

Addendum: the mention detector accepts any currency sign, not only $, so the workaround above ("avoid $name") is incomplete.

At c79ef8c (main), apps/server/src/provider/Drivers/ClaudeSkillDispatch.ts:

ts
1// lines 10-11
2const SKILL_MENTION_PATTERN =
3 /(^|\s)\p{Sc}(?![0-9][0-9_]*(?:[kKmMbBtT]|[eE][0-9]+)?(?:\s|$))(?=[a-zA-Z0-9:_-]*[a-zA-Z])([a-zA-Z0-9][a-zA-Z0-9:_-]*)(?=\s|$)/gu;

\p{Sc} is any Unicode currency symbol, and ClaudeSkillDispatch.test.ts (lines 43-60) dispatches mentions prefixed with € £ ¥ ₹ ₩ ₿ 𑿝. Amounts such as €20 or $20k are excluded by the lookahead. So in a Claude thread, an agent's message containing €technical-writing should dispatch the skill exactly as $technical-writing does. This follows from the source and its tests; we reproduced it live only with $, and we did not test other signs in Codex threads.

For the fix: scheduledTasks/webhookTemplate.ts already breaks these mentions in untrusted webhook values (line 52, /[\p{Sc}@]|(?<=^|\s)\/(?=[^\s/]+(?:\s|$))|.../giu, followed by a U+2060 word joiner at line 170). Applying the same treatment to agent-originated t3_thread_send text would cover every currency sign and a leading /name.

On our side, a change accepted today makes sitrep, the cross-session messaging tool we use, push any message containing a currency sign as a neutral pointer. The recipient reads the exact text through its hook or by asking sitrep. That only covers our own sender; other agents calling t3_thread_send directly are still affected.

Synced from GitHub

Another entry point for the same root cause, which the open fix (#24778) does not cover: delegated-task completion wakes.

When a delegate_task child finishes, the server inlines the child's result into the parent's wake message (delegatedCompletionWakeDetail). The Claude adapter sends that wake like any user turn, with the user-invocable skill map (L10754, L11203), so planClaudeSkillDispatch turns any $name in the child's output into a native skill command. #24778 neutralizes mentions only in t3_thread_send, and its description says delegation flows keep their current behavior.

What happened. A research child mined old transcripts and returned JSONL. One row quoted an earlier user prompt containing $reprompt, which is an installed skill. The Claude parent's next turn ran /reprompt with the rest of the child's output as its arguments, and the skill loaded as if the user had invoked it. The parent noticed and ignored it; nothing in the turn showed that a child wrote the mention.

The outgoing prompt.offer in the provider event log shows the split (redacted, lengths are the real ones):

text
1content[0].text (9809 chars): Delegated task <task-id> (mine routing cases: <host> (retry)) [completed]:
2 {..."quote":"... have it /reprompt itself, then have it tee it up for me",...}
3content[1].text (9119 chars): /reprompt itself, then have it tee it up for me","context":...}

content[1] is the tail of content[0] from the mention on. The child's own text had $reprompt; T3 rewrote it.

Still on the newest nightly. ClaudeSkillDispatch.ts has not changed since the incident build. Running planClaudeSkillDispatch from v0.1.7-nightly.20261103.4119 on a wake-shaped string, Delegated task t1 [completed]:\n{"quote":"have it $reprompt itself, then tee it up"}, with reprompt in the skill map returns commandText = /reprompt itself, then tee it up"}.

Version: incident on 0.1.4-nightly.20261028.3824 (Linux x64 server, Claude Code 2.1.312, Opus 5.5 parent, Codex child on a linked environment). Source read at v0.1.7-nightly.20261103.4119 (cefb544).

A fix at the wake boundary, such as the webhookTemplate.ts mention treatment, would cover this path. Question relays and other server-built messages that embed agent output may need the same treatment. I did not check Codex parents, which per the report above expand $name themselves.

Filed by: Claude Opus 5.5 (Claude Code), on behalf of the poster, who reviewed it before posting.