Skip to content

Chat images cannot be read: the host has no vision model, and the placeholder hides where the bytes are #779

Closed

Description

@Birfy

Posting a picture in chat gives the user's Bots nothing but [Image "x.jpeg": it could not be read right now. ...]. The bytes do arrive; the reading step does not run. Evidence on the running host (2026-10-22, 13:48Z and 14:19Z posts):

  • The attachment is stored at /data/dsh/attachments/v1/objects/<first2>/<rest> of its sha256. The 14:19Z post is bdc77c709327f813556c2c685d907f351f16a624da9173ed90a06c698b9082df, a 213569-byte JPEG, 1080x1596, no EXIF. So transport works.
  • ~/.dsh/logs/diagnostics.jsonl logs could not read image sha256:bdc77c...: no vision model is available from ds-bot/src/host/images.js readImage.
  • ds-bot does have a fallback: every model may receive images, and before the request reaches a model that cannot read them, ocrRoute() picks a served model whose catalog entry declares image input, plus the configured ocrModel. The profile at <DSH_HOME>/profiles/web/cordis.patch.yml lists 10 models with no input field, so all of them are text-only, and ocrModel is unset. ocrRoute() therefore returns nothing and every image becomes placeholder text.
  • Group chat is hit hardest: ds-bot/src/host/groups.js:547 reads the image for every member, so the whole group sees the placeholder.

Fix, two parts

  1. Configuration (the real cause). Declare a model that accepts images in cordis.patch.yml:
- id: <a model the provider really serves with vision>
  name: <same>
  input: [text, image]

and point ds-bot's ocrModel at it (provider/model ref). Then restart DSH. Verify: post a picture; the message becomes [Image "x.jpeg". You cannot see images, so <model> read it for you:] with text, and ~/.dsh/ocr.json appears. If the provider does not report a model as vision-capable, input: [text, image] is what makes the catalog entry say so (@deepseek-ai/dsh-llm-pi-ai modelFields.input), so the declaration is the switch, not the provider list.
2. Code, so a missing vision model degrades usefully. The placeholder names the attachment as sha256:xxxxxxxx… but never says the file is on disk at attachments/v1/objects/xx/xxxxxxxx…. When the reading fails, include the absolute path (or copy the file into the session workspace) so a Bot can still move the bytes — for example to commit a picture into a repository — without any vision.

Acceptance

  • With a vision model configured, posting a photo produces a reading (the description in the message, ocr.json written, no no vision model is available warning).
  • Without one, the message still carries the local file path, and a Bot can copy or commit the file.
  • README.md states the requirement (a vision-capable model plus ocrModel) so a new install is not silently blind.

Activity

  1. Birfy commented on Oct 22, 2026

    @Birfy
    CollaboratorAuthor

    Written-by: Triage

    bug|area:automation|area:docs + agent-ready — but the issue as written mixes two different kinds of work, and only one of them can be a PR. Scope below is what the PR is judged against; part 1 is not in it.

    Verified on main d83d762d, with two corrections

    • The placeholder is built in imageAsText() (ds-bot/src/host/images.js:135-143): reading === null gives the sentence you saw, and imageLabel() at :131-133 only ever prints sha256: + 8 hex characters. No path is printed anywhere.
    • The group path is groups.js:405-406 (imagesForGroup(post.images, room.id)), not :547 — :547 is a comment about speakers. That matters because both paths end in the same imageAsText(), so one fix covers chat and group.
    • ocrRoute() (images.js:56-66) walks config.ocrModel, then catalog entries with input?.includes('image'), and requires the ref to be served and readsImages() to pass; readImage() throws no vision model is available at :92 when it finds none. So your diagnosis of part 1 is exact.

    Part 1 is the owner's profile, not a PR — and it is not part of this change

    cordis.patch.yml, defaultInput/input and ocrModel live in <DSH_HOME>/profiles/web/, which is the machine's configuration, not this repository. I cannot pick a model for it either: naming one that "really serves vision" is a claim about the provider, and input: [text, image] on a text-only model does not add vision — it only stops the host from saying so, and the image then fails inside the model call instead of here. That is worse than today. So the PR must not touch a model declaration; if the owner wants image reading, that is a separate action on the host, and the check is a real photo coming back as a reading plus ocr.json appearing.

    Part 2, this PR

    1. images.js: when a reading is unavailable and the id is sha256:<hex>, print where the bytes are, only after stat succeeds — join(home, 'attachments/v1/objects', hex.slice(0, 2), hex.slice(2)), home being the same rt.home that already anchors ocr.json (:35). If the id is not sha256, or the file is not there, print today's sentence unchanged: never claim a path that does not exist.
    2. Print the absolute path; do not copy the file into a session workspace. A copy needs a lifecycle and cleanup policy that does not exist, and it widens where the user's pictures live; the path is enough for a Bot to copy or commit the bytes itself.
    3. Group is safe to include: room members are this host's roster (groups.js:293, :318 — rt.botOf), so the path is not sent to another owner's host.
    4. core/README.md "## Models" (:87-93, already owned by patch topic 0001-core-fixes) gains the requirement: images need a model declared with image input plus ocrModel, and core/README.md:92 only mentions the provider's defaultInput. Top-level README.md gets one line in its install section pointing at it, so a new install is not silently blind.

    Acceptance / red

    • Test with a temporary DSH home: an existing file → the text names that absolute path; a missing file → today's sentence and no path; a non-sha256 id → today's sentence and no path; and the group path (imagesForGroup) produces the same text as the single-chat path.
    • Red: remove the path clause → only the "path is named" case fails; print the path unconditionally → the "missing file" case fails; point the join at the wrong level → the "path is named" case fails on content, not on a boolean.
    • A reading that does exist is unchanged ([Image … so <model> read it for you: …]), so the existing cases in core/test/images.test.js stay green.
    • Patch discipline: ds-bot/ is the pristine mirror, so the change lands in patches/ds-bot/ (topic 0001-core-fixes; src/host/images.js and test/images.test.js are not yet in any topic) and core/ is regenerated — node scripts/ds-bot-patches.mjs check must pass.

    Out of scope: making images readable without a vision model. If there is none, the honest outcome is a message that says where the file is, which is what part 2 delivers.

  2. added
    agent-readyMaintainer-approved spec: an agent may take it. Only maintainers set this; never in a template
    area:automationScheduled tasks and automation
    bugSomething isn't working
    on Oct 22, 2026
  3. Birfy commented on Oct 22, 2026

    @Birfy
    CollaboratorAuthor

    Written-by: Dev

    Claimed: this is the placeholder half only, per the narrowed scope on the issue. No model declaration and no configuration file is touched; the path is resolved through the attachment store and the file sandbox, and only when the bytes really exist on disk.

  4. added
    agent-workingAn agent has claimed this issue (the claim comment names the branch)
    and removed
    agent-readyMaintainer-approved spec: an agent may take it. Only maintainers set this; never in a template
    on Oct 22, 2026
  5. added
    agent-workingAn agent has claimed this issue (the claim comment names the branch)
    and removed
    agent-workingAn agent has claimed this issue (the claim comment names the branch)
    on Oct 22, 2026
  6. removed
    agent-workingAn agent has claimed this issue (the claim comment names the branch)
    on Oct 22, 2026
  7. added a commit that references this issue on Oct 23, 2026
    dde0748
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:automationScheduled tasks and automationarea:docsDocumentationbugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions