Skip to content

fix(channels): image uploads via Telegram not visible to agent — base64 data URI approach broken #562

Description

@vybe

Summary

Files uploaded via Telegram (and other public-channel agents) are successfully downloaded by the backend, but images are never actually visible to the agent. Non-image files (text, CSV, JSON) work correctly via the `put_archive` + Read tool path. Images silently fail: the agent receives a long base64 string in its text prompt but has no visual access to the image content.

Context

PR #494 (closing #487) shipped Phase 2 of file delivery. It correctly handles non-image files by writing them to `/home/developer/uploads/{session}/` via Docker's `put_archive` and adding `Read` to the agent's allowed tool list.

For images, the PR chose a different path: embed the image as a base64 data URI in the text prompt:
```
photo.jpg
```

This was chosen because using the `Read` tool on binary image files with `--allowedTools` causes Claude Code to crash (API returns 400 "Could not process image", process exits without flushing stdout).

However, the base64 data URI approach is also broken: Claude Code CLI passes stdin as a text message to the Claude API. Markdown image syntax with data URIs is not parsed into image content blocks — Claude receives a long text string and cannot see the image visually.

The manual verification steps in PR #494 were left unchecked:

  • Manual: send photo to a Telegram bot, verify agent describes the upload

Acceptance Criteria

  • Agent can visually process images sent via Telegram (photos and image documents)
  • Works within the public-channel allowed-tools security model (no unrestricted Write/Bash/Edit for unauthenticated users)
  • No crash when agent processes an uploaded image
  • Consistent behavior for all image types supported by Telegram (JPEG, PNG, WebP)
  • Non-image file delivery (text, CSV, JSON) is unaffected

Technical Notes

Root cause: Two-part mismatch in the image path in `message_router._handle_file_uploads`:

  1. Base64 data URI in text ≠ image content block in the Claude API → agent sees text, not image
  2. Read tool + binary image file + `--allowedTools` → Claude Code crash (400 from API)

Files involved:

  • `src/backend/adapters/message_router.py` — `_handle_file_uploads()` lines 816–850 (base64 embed path)
  • `docker/base-image/agent_server/services/claude_code.py` — stdin write at line 607

Candidate solutions (evaluate in order):

  1. `--image` flag — Check if current Claude Code CLI supports `--image /path/to/file` alongside `--print`. If yes: write image to container via `put_archive` and pass the path via `--image` instead of stdin embedding. No Read tool needed, no crash.
  2. JSON stdin with image content block — Claude Code may support a structured JSON input format (instead of plain text) that includes `image` content blocks with base64 data. Check Claude Code SDK/stdin format spec.
  3. Pre-describe via vision model — Before routing to agent, run image through a quick vision call (Claude or Gemini) to get a text description; inject the description into the prompt. Works regardless of Claude Code version, but adds latency and loses pixel-level fidelity.
  4. Lift Read restriction for images on verified users — For Telegram users who completed `/login` (verified email), relax the tool restriction to allow Read on images. Write the image to container normally. Investigate why the crash happens and whether it's fixable (may be a Claude Code version-specific issue).

Related

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions