Skip to content

[Bug]: /acp text commands inside a bound Discord thread get swallowed by the ACP LLM session instead of reaching handleAcpCommand #66298

@kindomLee

Description

@kindomLee

Bug type

Behavior bug (incorrect output/state without crash)

Beta release blocker

No

Summary

/acp close (and other /acp text commands) issued inside a Discord thread with an active ACP binding never reach handleAcpCommand — they are dispatched to the thread's ACP session and consumed as conversational input by the ACP agent, producing a spurious natural-language reply without actually running the command.

Steps to reproduce

  1. Configure channels.discord.threadBindings.enabled: true and channels.discord.threadBindings.spawnAcpSessions: true.
  2. In a Discord guild text channel, spawn a thread-bound ACP session (e.g. /acp spawn --thread here with agent claude). Confirm /root/.openclaw/discord/thread-bindings.json contains the new binding entry.
  3. Inside the newly-created bound Discord thread, send /acp close as a message (plain text, Discord autocomplete, or slash command dropdown).
  4. Observe the bot reply, the gateway logs, and /root/.openclaw/discord/thread-bindings.json.

Expected behavior

handleAcpCloseAction runs, the ACP session is closed, the binding entry is removed from thread-bindings.json, and the bot replies with ✅ Closed ACP session <key>. Removed 1 binding. — the string built at dist/lifecycle-FoOPYxGk.js:566.

Actual behavior

  • handleAcpCloseAction is never invoked. grep -E "Closed ACP|Removed.*binding|unbind" /root/clawd/logs/openclaw.log returns zero matches for the entire session window.
  • thread-bindings.json entry is unchanged after the attempt.
  • The bot replies with a short natural-language message like done instead. This is the ACP agent (MiniMax-M2.7 in my case, but any model would exhibit the same hallucination) interpreting the literal text /acp close as a conversational instruction and replying politely. The ACP session remains active and continues routing subsequent thread messages.

OpenClaw version

2026.4.11

Operating system

Ubuntu 22.04.5 LTS (aarch64, Oracle Cloud Always Free VM)

Install method

npm global (/usr/lib/node_modules/openclaw@2026.4.11, launched by systemd openclaw.service)

Model

minimax/MiniMax-M2.7 (not model-specific — any ACP agent will produce a hallucinated natural-language reply to /acp close because the text is routed to it as user input)

Provider / routing chain

MiniMax as primary agent model; Discord channel → spawned thread (bound via spawnAcpSessions: true) → embedded acpx runtime backend → @agentclientprotocol/claude-agent-acp@^0.25.0.

Logs, screenshots, and evidence

Grep of the gateway log across the full window when the user attempted /acp close multiple times:

$ sudo grep -E "Closed ACP|Removed.*binding|unbind" /root/clawd/logs/openclaw.log
(no output)

Relevant code path in the compiled distribution: dist/dispatch-acp-command-bypass-CNBjqOaQ.js::shouldBypassAcpDispatchForCommand:

function shouldBypassAcpDispatchForCommand(ctx, cfg) {
    const candidate = resolveCommandCandidateText(ctx);
    if (!candidate) return false;
    const normalized = candidate.trim();
    const allowTextCommands = shouldHandleTextCommands({
        cfg,
        surface: ctx.Surface ?? ctx.Provider ?? "",
        commandSource: ctx.CommandSource
    });
    if (!normalized.startsWith("/") && maybeResolveTextAlias(candidate, cfg) != null) return allowTextCommands;
    if (isResetCommandCandidate(normalized)) return true;   // /new, /reset
    if (!normalized.startsWith("!")) return false;          // ← /acp falls through here
    if (!ctx.CommandAuthorized) return false;
    if (!isCommandEnabled(cfg, "bash")) return false;
    return allowTextCommands;
}

Only /new, /reset, and !-prefix commands bypass the ACP dispatch. /acp ... is not in the bypass list, so any /acp text sent inside a bound thread is handed off to the thread's ACP session instead of being treated as a gateway command.

Impact and severity

Medium-high UX bug. Users cannot close, cancel, steer, change mode, or check status of an ACP session from inside the bound thread where the session is actually running — the most natural place to do so. Every /acp attempt produces a confusing "it replied, but nothing changed" UX: the LLM agent's polite done/好的 hallucination looks like the command succeeded, which compounds the confusion. The only workaround I found was editing thread-bindings.json by hand and restarting the gateway.

Additional information

Confirmed the fix locally by adding /acp to the bypass list:

 if (isResetCommandCandidate(normalized)) return true;
+// bypass ACP dispatch for /acp so it reaches handleAcpCommand inside bound threads
+if (/^\/acp(?:\s|$)/i.test(normalized)) return allowTextCommands;
 if (!normalized.startsWith("!")) return false;

After this one-line change plus a gateway restart, /acp close issued inside a bound thread reaches handleAcpCommandhandleAcpCloseActionacpManager.closeSessiongetSessionBindingService().unbind, and thread-bindings.json count drops from 2 → 1 as expected.

Related but not duplicate: #59026 is about the inverse direction (parent-channel messages being misrouted to thread after task completion); this issue is about /acp text commands inside the thread never reaching the command handler at all.

I am happy to submit a PR for the above one-line fix if the fix direction is acceptable.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions