Pull requests / #2641
#2641 serve: a tool's picture starts the elastic encoder, and its turn counts
open · @jerry78424 · 0 commentaires · Sur GitHub
Description
## Title Bug: an idle elastic image encoder + a tool's picture = the conversation dies (400 "malformed") instead of the documented note ## Summary `_note_unreadable_tool_images` - the method that promises "a picture a tool returned that this server cannot read becomes a short note in its place instead of a 400" - encodes the picture without starting the elastic encoder first. Once the idle sweep has stopped the encoder (`elastic_idle_s` without a picture, default 120 s), the tool's picture writes to a pipe that no longer exists: the `AttributeError` on the `None` proc escapes the `except (ValueError, OSError)` filter and the route's body-shape filter, and the request dies as a **400 "the request is malformed (AttributeError...)"** - the opposite of what the method exists to do. ## Reproduce `"elastic": true` + `"vram_elastic": true`; send one picture, let the encoder idle-stop (log: `[strata] images: the image encoder stopped after N s ...`); then send a `tool` message whose content carries an `image` block (Claude Code's Read of a picture - the exact input 3709c15f added this method for). Expected per the method's own docstring: a `[image omitted: ...]` note. Actual: `AttributeError: 'NoneType' object has no attribute 'stdin'` (inside `Vision.encode`'s `self.proc.stdin.write`) surfaces as a 400 - and because clients re-send the whole history every turn, every later request of the conversation dies the same way at this method's own line, before the user-picture encode below could start the encoder: the conversation stays broken until a picture from another conversation (or a restart) starts the encoder. The prepare() path does not have this problem - its encode block calls `_vision_up()` inside the FIFO turn; the tool path (3709c15f, wrapped into `_turn` by 873a0d66) never got the wake, and the elastic idle stop made the missing wake fatal. ## What changed - `serve/server.py`: inside the tool path's turn, `_vision_up()` before the encode (the caller holds the FIFO, as prepare does); the turn's end updates `last_image_at` in a `finally` - without it the idle sweep judges the just-started encoder by the previous picture's timestamp and reclaims it at once, and a tool-picture-only conversation never moves the timer; `start()`'s `RuntimeError` joins the filter, so a failing start becomes the documented note, not a dead request. - `serve/test_vision_elastic.py`: two cases - a tool picture wakes a stopped encoder, and the turn holds it across the idle check; a start that raises becomes the note. ## How I checked Both new cases fail on the base tree at the fake's "a picture reached an encoder that was not started" assert (the bug's exact signature), and a real `Vision` with the proc unloaded raises the `AttributeError` verbatim; with the change `test_vision_elastic` 9/9, with `test_vision_busy` / `test_images` 32 OK. `encode()`'s own pipe death already becomes ValueError/OSError internally (#1317's read timeout raises ValueError; a write to a dead pipe is OSError), and `ClientGone` is a bare Exception, so the widened filter matches what can still raise and a client that left still propagates. No native files touched. ## Related The elastic idle stop, #2435 (the sibling: a start that fails on the prepare path), #2343 (MCP picture results will send more traffic through this exact path). Independent of #2455 (its `cached()` skip lands in this same method later - textual overlap only).
Sur le site
Liens install, modèles, releases.