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

opened3d 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.