Skip to content

Telegram bot: canUseTool returns wrong schema (result/reason instead of behavior/updatedInput/message) — ZodError blocks every tool call #1433

Description

@ichoosetoaccept

Summary

Every tool call the Telegram bot (PULSE/modules/telegram.ts) makes fails with a ZodError from the Claude Agent SDK's permission-response validation. This affects both the allow and the deny path — Donna (the DA) can chat, but cannot Read/Write/Bash anything via Telegram.

Environment

  • LifeOS version: 6.0.5
  • @anthropic-ai/claude-agent-sdk: 0.1.77 (installed; package.json pins ^0.1.0) — this is the package whose response-schema validation is throwing
  • Claude Code CLI: was 2.0.77 at the time this was captured (per the SDK session transcript's version field); the CLI has since self-updated to 2.1.201, unrelated to this bug
  • Linux, Pulse running as a systemd --user service (headless)

Root cause

canUseTool in PULSE/modules/telegram.ts (around line 843) returns the wrong response shape:

canUseTool: (toolName: string, input: unknown) => {
  if (toolName === "Bash") {
    // ...
    if (cmd.includes("31337") || cmd.includes("/notify")) {
      return { result: "deny", reason: "..." }   // <-- wrong keys
    }
  }
  return { result: "allow" }                       // <-- wrong key, missing updatedInput
}

The installed SDK validates the callback's return value against a schema that expects:

{ behavior: "allow", updatedInput: {...} }
{ behavior: "deny", message: "..." }

result/reason matches neither variant of the union, so every tool-permission check throws, regardless of whether the intent was allow or deny. Captured directly from the SDK session transcript:

Tool permission request failed: ZodError: [
  {
    "code": "invalid_union",
    "errors": [
      [
        { "code": "invalid_value", "values": ["allow"], "path": ["behavior"], "message": "Invalid input: expected \"allow\"" },
        { "expected": "record", "code": "invalid_type", "path": ["updatedInput"], "message": "Invalid input: expected record, received undefined" }
      ],
      [
        { "code": "invalid_value", "values": ["deny"], "path": ["behavior"], "message": "Invalid input: expected \"deny\"" },
        { "expected": "string", "code": "invalid_type", "path": ["message"], "message": "Invalid input: expected string, received undefined" }
      ]
    ],
    "path": [],
    "message": "Invalid input"
  }
]

Reproduction

  1. Run Pulse with the Telegram module enabled.
  2. Message the bot with anything that requires a tool call, e.g. "read ~/.claude/LIFEOS/USER/TELOS/TELOS.md and tell me the first line."
  3. The tool call fails immediately with the ZodError above. Plain conversational replies (no tool use) still work, which makes this easy to miss until you ask for something that needs a file read.

Fix

-              return { result: "deny", reason: "Telegram mode: /notify and port 31337 are blocked. Voice is delivered via Telegram sendVoice, not the desktop speaker." }
+              return { behavior: "deny", message: "Telegram mode: /notify and port 31337 are blocked. Voice is delivered via Telegram sendVoice, not the desktop speaker." }
             }
           }
-          return { result: "allow" }
+          return { behavior: "allow", updatedInput: (input as Record<string, unknown>) ?? {} }

Applied and tested locally — confirmed via the raw SDK session transcript that both the allow path (a real Read of a TELOS file now returns actual file content) and the deny path (a blocked /notify health-check now returns a clean deny message instead of crashing) work correctly after this change.

Suggested fix

Same two-line change as above in PULSE/modules/telegram.ts. Worth grepping the rest of the codebase for other canUseTool/permission-callback implementations using the same old result/reason shape, in case this schema drifted in more than one place.

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