#814·MiroFish

bug: Three reproducible bugs: None-content crash, negative max_tokens, stale image error handling

Author: tiagouzlCreated Sep 16, 2026Updated Sep 16, 2026

Three reproducible bugs found while running self-referential simulations (MiroFish analyzing its own launch/reception)

Setup: Local Docker deployment (ghcr.io/666ghj/mirofish:latest), LLM backend = openai/gpt-oss-20b via NVIDIA API (OpenAI-SDK-compatible endpoint), Zep Cloud free tier for memory.


Bug 1: LLMClient.chat() crashes on content=None from reasoning models

Error:

报告生成失败: expected string or bytes-like object, got 'NoneType'

When it happens: Deterministically during Step 4/5 (report generation), right after the SECTION START log of the first outline section (reproduced on two independent runs, different seeds/outlines, identical failure point).

Root cause (verified in shipped image code, backend/app/utils/llm_client.py, chat()):

content = response.choices[0].message.content
content = re.sub(r'<think>[\s\S]*?</think>', '', content).strip()

Reasoning models served through OpenAI-compatible endpoints (observed: gpt-oss-20b via NVIDIA) sometimes return the output in reasoning_content with message.content = None. re.sub() on None raises the TypeError above. There is no None guard anywhere on this path.

Suggested fix: Fall back to reasoning_content when content is None (verified working locally):

content = message.content
if content is None:
    content = getattr(message, 'reasoning_content', None) or ''

Impact: Affects any reasoning model on this code path, regardless of provider.


Bug 2: Unclamped token budget goes negative on long simulations (observed, exact computation site unverified)

Error (from simulation.log):

openai.BadRequestError: Error code: 400 - {'error': {... 'max_tokens must be at least 1, got -365748' ...}}

When it happens: Round 94/120 of a long run, as accumulated context grows. (Hypothesis, not yet traced to a line: budget computed as context-window minus prompt size with no lower-bound clamp. The 400 and the negative value are confirmed from logs; the exact computation site still needs tracing — likely in the simulation runner or OASIS layer.)

Suggested fix: Clamp to a sane minimum before the API call and/or truncate/summarize history near the context limit.

Impact: Any sufficiently long run will eventually fail mid-run on any provider.


Bug 3: Shipped Docker image lags main — dead models surface as generic 500

Observations (both verified):

  1. openai/gpt-oss-120b returns 410 Gone on NVIDIA ("has reached its end of life on 2026-09-03") — any config pointing at it fails.
  2. The shipped image's backend/app/api/graph.py has no provider-error branches (no 502 / LLMResponseError handling), while current GitHub main does. So provider failures (410, 429, 400…) surface to the user as a generic 500, with no actionable message and no traceback in either docker logs or the in-container log file (the except block logs only str(e)).

Suggested fix: Rebuild/pin the image closer to main, and backport the 502-with-provider-status error mapping so dead keys/models/quotas produce diagnosable errors instead of 500.


Additional context (non-bug finding): seed wording drives ontology reification

A/B on seed design, same tool + model: seed naming concrete products → ontology collapsed to 2 agents (the named entities), ~19 events/40 rounds. Same content rewritten with social roles instead of proper nouns → 21 entities, 8 role-based agents, 168 events before Bug 2 hit (~9x activity). Worth a note in seed-writing docs.

Happy to share the two seed files and simulation IDs (sim_428bbb515066, sim_f29e23670506)

seed-mirofish-analisa-mirofish-v2.md