Skip to content

PAI/PULSE/modules/telegram.ts: short messages produce empty replies (numTurns=0); literal {{DA_NAME}}/{{PRINCIPAL_NAME}} reach SDK and chat-log #1143

@jmmarkiewicz

Description

@jmmarkiewicz

Summary

Two related runtime bugs in PAI/PULSE/modules/telegram.ts that bite together: short user replies ("Agreed", "OK", "thanks") return the fallback "Sorry, I wasn't able to generate a response. Try again?" instead of a real response. And the bot's runtime systemPrompt has literal {{DA_NAME}} / {{PRINCIPAL_NAME}} placeholder strings — the substitution gap #1135 calls out for PAI_SYSTEM_PROMPT.md is not applied to TypeScript source either.


Bug 1 — SDK session resume + manual history-prepend produces numTurns: 0 on short messages

telegram.ts:182-188 builds a prompt that prepends the last 10 messages of conversation history as plain text:

prompt = `Previous conversation:\n${historyText}\n\nPrincipal's new message: ${sanitized}`

telegram.ts:218-221 ALSO sets sdkOptions.resume = lastSessionId, resuming the prior agent session that already contains the same history:

if (lastSessionId) {
  sdkOptions.resume = lastSessionId
}

So the SDK gets the conversation history twice. For long content-rich messages it muddles through. For short acknowledgments after a complex multi-turn previous response, the resumed agent loop short-circuits with numTurns: 0, cost: 0 after ~3s — the agent sees the new "Agreed" as redundant against its existing transcript and never issues a model call. The SDK returns no streamed text, fullText stays empty at telegram.ts:293, and telegram.ts:294 substitutes the "Sorry, I wasn't able..." fallback string.

Receipts

Six consecutive turns from one session, all sharing the same sessionId:

User message numTurns cost ($) Outcome
(37 chars, work) 1 0.27 OK
(38 chars) 1 0.28 OK
(130 chars) 13 0.86 OK
(998 chars) 1 0.40 OK
Agreed (6 chars) 0 0 fallback
I agreed to what you proposed (29 chars) 0 0 fallback

Pulse log lines for the failing turn:

"Message received" textLength=6
"Session initialized" sessionId=d0eb813d-...
"SDK session complete" durationMs=3088 numTurns=0 cost=0 sessionId=d0eb813d-...
"Empty response from SDK"
"Response sent" responseLength=55

Suggested fix

Pick one of the two history paths, not both:

  • Option A — drop resume. Each turn starts fresh; the manually-prepended history is the only context. Simpler and avoids the SDK's "this session already produced an answer" short-circuit.
  • Option B — keep resume, drop the manual prepend. Trust the SDK's session memory to carry context.

I'd lean toward Option A for predictability — the resumed-session code path also accumulates max-turns budget across resumes, which is a separate latent issue.


Bug 2 — Literal {{DA_NAME}} / {{PRINCIPAL_NAME}} strings reach the SDK and the chat log

telegram.ts:201-214 constructs the systemPrompt body with hardcoded placeholder strings that are never substituted at runtime:

append: `\n\n## TELEGRAM MODE OVERRIDE (highest priority  overrides CLAUDE.md format rules)

You are {{DA_NAME}}, responding via Telegram. {{PRINCIPAL_NAME}} is messaging you from his phone.

CRITICAL RULES FOR TELEGRAM MODE:
- ...
- Speak as {{DA_NAME}}  first person, natural, conversational, like talking to a friend
...

So the bot's runtime system prompt literally contains the strings {{DA_NAME}} and {{PRINCIPAL_NAME}}. Same issue in the chat-log writer at telegram.ts:71-79:

const entry = `\n### ${ts}\n**{{PRINCIPAL_NAME}}:** ${userMsg}\n\n**{{DA_NAME}}:** ${botMsg}\n\n---\n`

Result in ~/.claude/PAI/PULSE/logs/telegram/chat-log.md:

**{{PRINCIPAL_NAME}}:** Agreed
**{{DA_NAME}}:** Sorry, I wasn't able to generate a response. Try again?

Reproduce

grep -nE '\{\{[A-Z_]+\}\}' ~/.claude/PAI/PULSE/modules/telegram.ts
# lines 77 (chat log entry), 201, 203, 209, 211 (systemPrompt body)

tail -20 ~/.claude/PAI/PULSE/logs/telegram/chat-log.md
# shows literal {{DA_NAME}} / {{PRINCIPAL_NAME}} in entries

Suggested fix

Either substitute the placeholders at module init (read identity from PAI/USER/DA_IDENTITY.md / PRINCIPAL_IDENTITY.md like the installer does for other files), or replace the placeholder strings with abstract role labels — "the DA" / "the principal" — since the system-prompt context is functional, not display.

Note: #1135's installer-side fix won't reach this code path because the strings are in TypeScript source, not in PAI_SYSTEM_PROMPT.md. This needs source-code substitution (or a build-time codegen step), not just a fix in engine/actions.ts.


Environment

PAI v5.0.0, Bun 1.3.9, macOS, claude-agent-sdk 0.1.x.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions