openclaw - 💡(How to fix) Fix Telegram message_tool_only replies bypass normal reply decorator, so /usage footer is not shown [1 pull requests]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

On 2026.5.26 (10ad3aa), /usage tokens / /usage full can be persisted for a Telegram session and model usage is recorded, but Telegram replies sent through the message tool path do not show the usage footer.

This appears to happen because Telegram message_tool_only source replies bypass the normal internal source-reply/final-payload path where responseUsage is appended. The same path is only used for webchat today.

Root Cause

This appears to happen because Telegram message_tool_only source replies bypass the normal internal source-reply/final-payload path where responseUsage is appended. The same path is only used for webchat today.

Fix Action

Fixed

Code Example

Usage: 404 in / 8 out

---

function shouldUseInternalSourceReplySink(input, params) {
  return input.action === "send" &&
    input.sourceReplyDeliveryMode === "message_tool_only" &&
    normalizeOptionalLowercaseString(input.toolContext?.currentChannelProvider) === "webchat" &&
    Boolean(input.sessionKey?.trim()) &&
    !hasExplicitRouteParam(params);
}

---

import { n as runMessageAction } from './dist/message-action-runner-kdd1czMt.js';

await runMessageAction({
  cfg: { tools: { message: { actions: { send: true } } } },
  action: 'send',
  params: { message: 'OK', dryRun: true },
  toolContext: { currentChannelProvider: 'webchat', currentChannelId: 'source-chat' },
  sourceReplyDeliveryMode: 'message_tool_only',
  sessionKey: 'agent:main:telegram:direct:8223071058',
  agentId: 'main',
  dryRun: true
});
// => handledBy: internal-source

await runMessageAction({
  cfg: { tools: { message: { actions: { send: true } } } },
  action: 'send',
  params: { message: 'OK', dryRun: true },
  toolContext: { currentChannelProvider: 'telegram', currentChannelId: 'source-chat' },
  sourceReplyDeliveryMode: 'message_tool_only',
  sessionKey: 'agent:main:telegram:direct:8223071058',
  agentId: 'main',
  dryRun: true
});
// => falls through into Telegram target resolution instead of internal-source

---

const responseUsageMode = resolveResponseUsageMode(
  activeSessionEntry?.responseUsage ??
  (sessionKey ? activeSessionStore?.[sessionKey]?.responseUsage : void 0)
);
if (responseUsageMode !== "off" && hasNonzeroUsage(usage) && !preserveUserFacingSessionState) {
  // builds responseUsageLine
}
...
if (responseUsageLine) finalPayloads = appendUsageLine(finalPayloads, responseUsageLine);

---

const next = {
  ...existing,
  text: `${existingText}${separator}${line}`
};
RAW_BUFFERClick to expand / collapse

Summary

On 2026.5.26 (10ad3aa), /usage tokens / /usage full can be persisted for a Telegram session and model usage is recorded, but Telegram replies sent through the message tool path do not show the usage footer.

This appears to happen because Telegram message_tool_only source replies bypass the normal internal source-reply/final-payload path where responseUsage is appended. The same path is only used for webchat today.

Environment

  • OpenClaw: 2026.5.26 (10ad3aa)
  • Install: npm global under ~/.local/lib/node_modules/openclaw
  • Runtime: gateway systemd user service
  • Channel: Telegram DM
  • Session key: agent:main:telegram:direct:<telegram-user-id>
  • Session setting observed: responseUsage: "tokens"
  • Recent session usage observed: nonzero inputTokens / outputTokens

Expected behavior

After running /usage tokens or /usage full, Telegram agent replies should include the usage footer, e.g.

Usage: 404 in / 8 out

or the full footer with session information when full is selected.

Actual behavior

Telegram visible replies are delivered, but no usage footer is appended. The session store shows that the usage setting is saved and usage metadata exists, so this is not a command persistence issue.

Source-level findings

1. Telegram source replies do not use the internal source-reply sink

In the built package, message-action-runner-kdd1czMt.js has:

function shouldUseInternalSourceReplySink(input, params) {
  return input.action === "send" &&
    input.sourceReplyDeliveryMode === "message_tool_only" &&
    normalizeOptionalLowercaseString(input.toolContext?.currentChannelProvider) === "webchat" &&
    Boolean(input.sessionKey?.trim()) &&
    !hasExplicitRouteParam(params);
}

Because this is gated to webchat, Telegram current-source replies in message_tool_only mode fall through to the normal channel message action instead of becoming an internal source reply payload.

Local dry-run probe:

import { n as runMessageAction } from './dist/message-action-runner-kdd1czMt.js';

await runMessageAction({
  cfg: { tools: { message: { actions: { send: true } } } },
  action: 'send',
  params: { message: 'OK', dryRun: true },
  toolContext: { currentChannelProvider: 'webchat', currentChannelId: 'source-chat' },
  sourceReplyDeliveryMode: 'message_tool_only',
  sessionKey: 'agent:main:telegram:direct:8223071058',
  agentId: 'main',
  dryRun: true
});
// => handledBy: internal-source

await runMessageAction({
  cfg: { tools: { message: { actions: { send: true } } } },
  action: 'send',
  params: { message: 'OK', dryRun: true },
  toolContext: { currentChannelProvider: 'telegram', currentChannelId: 'source-chat' },
  sourceReplyDeliveryMode: 'message_tool_only',
  sessionKey: 'agent:main:telegram:direct:8223071058',
  agentId: 'main',
  dryRun: true
});
// => falls through into Telegram target resolution instead of internal-source

The second call failed locally with Unknown target "source-chat" for Telegram, which is useful evidence that Telegram did not take the internal source-reply sink path. In a real Telegram turn, the current channel id is resolvable, so the reply can still be sent, but it is sent before the normal reply decorator can append usage.

2. The usage footer is appended only in the normal final payload path

In agent-runner.runtime-BWGj8NfM.js:

const responseUsageMode = resolveResponseUsageMode(
  activeSessionEntry?.responseUsage ??
  (sessionKey ? activeSessionStore?.[sessionKey]?.responseUsage : void 0)
);
if (responseUsageMode !== "off" && hasNonzeroUsage(usage) && !preserveUserFacingSessionState) {
  // builds responseUsageLine
}
...
if (responseUsageLine) finalPayloads = appendUsageLine(finalPayloads, responseUsageLine);

So if the visible Telegram reply is delivered directly by message(action=send), the footer is not appended to that already-sent visible message.

3. Potential secondary issue: footer append replaces payload object

appendUsageLine currently creates a replacement object:

const next = {
  ...existing,
  text: `${existingText}${separator}${line}`
};

If the source reply has WeakMap metadata such as deliverDespiteSourceReplySuppression or sourceReplyTranscriptMirror, replacing the object without copying metadata can drop the metadata needed by message_tool_only delivery. This may matter if Telegram source replies are routed back through the internal sink.

Suggested fix direction

For message_tool_only turns, default current-source replies from any channel with current source context should probably use the internal source-reply sink, not only webchat, while preserving explicit-target sends as direct channel sends.

A narrow shape would be:

  • In shouldUseInternalSourceReplySink, replace the currentChannelProvider === "webchat" condition with a check for current source context (currentChannelProvider, currentChannelId, currentThreadTs, or currentMessageId).
  • Keep !hasExplicitRouteParam(params) so explicit target / to / channel sends still go through normal message delivery.
  • In appendUsageLine, preserve reply payload metadata when replacing the payload object, e.g. via copyReplyPayloadMetadata(existing, next).

Impact

/usage appears to work from the command reply and session state, but users on Telegram do not see the configured usage footer for Codex/OpenClaw bridge replies. This makes the command misleading and removes the intended per-reply token/cost visibility.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

After running /usage tokens or /usage full, Telegram agent replies should include the usage footer, e.g.

Usage: 404 in / 8 out

or the full footer with session information when full is selected.

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING