Summary
createFollowupRunner unconditionally routes through runEmbeddedPiAgent, skipping the CLI-runtime dispatch branch the primary reply path uses. Any queued followup whose model resolves to a CLI runtime (claude-cli, codex-cli, google-gemini-cli) throws Requested agent harness "<runtime>" is not registered. and the queued message is silently dropped.
User-visible symptom: on bursty Signal threads (and any channel with queued followup delivery), only the most recent inbound that arrives while the agent is idle gets processed. Anything that lands mid-turn is queued, the drain crashes, and those messages vanish without surfacing an error.
Root cause
Throw site: selection-61FIEezO.js:9781-9799
const forced = pluginHarnesses.find((entry) => entry.id === runtime);
if (forced) return ...;
throw new Error(`Requested agent harness "${runtime}" is not registered.`);
Primary reply path (agent-runner.runtime-Dv_D_ax0.js:1030-1083) correctly checks for CLI runtimes first and routes them through runCliAgent:
const cliExecutionProvider = resolveCliRuntimeExecutionProvider(...) ?? provider;
if (isCliProvider(cliExecutionProvider, runtimeConfig)) {
const result = await runCliAgent(...);
}
Followup queued runner (agent-runner.runtime-Dv_D_ax0.js:3034-3047) skips that branch entirely and always calls embedded Pi:
const fallbackResult = await runWithModelFallback({
...
run: async (provider, model, runOptions) => {
...
const result = await runEmbeddedPiAgent({ ... });
Embedded harness selection then looks up claude-cli (or codex-cli/google-gemini-cli) as a harness id — but those are registered as CLI backends by their plugins, not as runtime harnesses. Anthropic plugin registration:
register.runtime-CR-c-hRh.js:403-405 → api.registerCliBackend(buildAnthropicCliBackend())
- Manifest
extensions/anthropic/openclaw.plugin.json:54-56 → cliBackends entry, not a runtime harness
How to reproduce
- Configure a session whose
agents.defaults.models[<id>].agentRuntime.id is claude-cli (or codex-cli/google-gemini-cli).
- Send a message that triggers a long-running turn (tools, subagents).
- While that turn is in flight, send a second message on the same channel. It enters the followup queue.
- When the primary turn completes, the followup drain runs → throws
Requested agent harness "claude-cli" is not registered. → queued message is dropped.
Affects every queued lane that goes through createFollowupRunner: followup, collect, steer-backlog, cron/system-event followups.
Note: parent/child sharing the same runtime does not avoid the bug if that runtime is a CLI runtime. The dispatcher problem is independent of inheritance — it triggers whenever the queued lane resolves to any CLI runtime id.
Recommended fix (upstream)
In createFollowupRunner, before runEmbeddedPiAgent, perform the same CLI check the primary path does:
const cliExecutionProvider = resolveCliRuntimeExecutionProvider(...) ?? provider;
if (isCliProvider(cliExecutionProvider, runtimeConfig)) {
return runCliAgent(...); // with the queued run params
}
return runEmbeddedPiAgent(...);
Keeps claude-cli / codex-cli / google-gemini-cli as CLI backends instead of pretending they're harnesses.
Risk: affects all queued lanes — followup, collect, steer-backlog, cron/system-event followups, and any lane draining queued replies. Tests should cover:
- Signal followup delivery for CLI runtimes
- CLI session binding reuse across the drain
- Fallback to embedded Pi for non-CLI runtimes (regression check)
- Duplicate-delivery semantics through the CLI output bridge
Summary
createFollowupRunnerunconditionally routes throughrunEmbeddedPiAgent, skipping the CLI-runtime dispatch branch the primary reply path uses. Any queued followup whose model resolves to a CLI runtime (claude-cli,codex-cli,google-gemini-cli) throwsRequested agent harness "<runtime>" is not registered.and the queued message is silently dropped.User-visible symptom: on bursty Signal threads (and any channel with queued followup delivery), only the most recent inbound that arrives while the agent is idle gets processed. Anything that lands mid-turn is queued, the drain crashes, and those messages vanish without surfacing an error.
Root cause
Throw site:
selection-61FIEezO.js:9781-9799Primary reply path (
agent-runner.runtime-Dv_D_ax0.js:1030-1083) correctly checks for CLI runtimes first and routes them throughrunCliAgent:Followup queued runner (
agent-runner.runtime-Dv_D_ax0.js:3034-3047) skips that branch entirely and always calls embedded Pi:Embedded harness selection then looks up
claude-cli(orcodex-cli/google-gemini-cli) as a harness id — but those are registered as CLI backends by their plugins, not as runtime harnesses. Anthropic plugin registration:register.runtime-CR-c-hRh.js:403-405→api.registerCliBackend(buildAnthropicCliBackend())extensions/anthropic/openclaw.plugin.json:54-56→cliBackendsentry, not a runtime harnessHow to reproduce
agents.defaults.models[<id>].agentRuntime.idisclaude-cli(orcodex-cli/google-gemini-cli).Requested agent harness "claude-cli" is not registered.→ queued message is dropped.Affects every queued lane that goes through
createFollowupRunner: followup, collect, steer-backlog, cron/system-event followups.Note: parent/child sharing the same runtime does not avoid the bug if that runtime is a CLI runtime. The dispatcher problem is independent of inheritance — it triggers whenever the queued lane resolves to any CLI runtime id.
Recommended fix (upstream)
In
createFollowupRunner, beforerunEmbeddedPiAgent, perform the same CLI check the primary path does:Keeps
claude-cli/codex-cli/google-gemini-clias CLI backends instead of pretending they're harnesses.Risk: affects all queued lanes — followup, collect, steer-backlog, cron/system-event followups, and any lane draining queued replies. Tests should cover: