Skip to content

fix(cli): one tool's reader failing must not fail the whole local read #689

Description

@blafourcade

Context

aidd telemetry read asks every declared tool's reader in turn. OpencodeCostReaderAdapter shells out to opencode export <session> --sanitize with a ten-second budget, and throws OpencodeExportError for any failure that is not the exact string "session not found" — including a timeout, where spawnSync sets result.error.

ReadLocalCostUseCase does not catch. So one tool being slow, hung, or broken aborts the read for every tool, and the records the other readers already produced are never stored.

Found by

A new e2e for aidd telemetry report seeded its sink by running telemetry read first. It passed alone and failed roughly one full-suite run in three, at 8-16 seconds — opencode is installed on this machine, so every run shelled out to it under whatever load the rest of the suite was producing. The e2e was changed to seed the sink directly; the product behaviour it exposed is untouched.

Expected

A reader that cannot answer costs that tool's figures, never the others'. It is the same rule the local-read contract already states for a missing transcript ("a missing, unreadable or truncated source costs attribution, not the figures") and the same rule the run-journal reader already obeys.

The tool should report as unread, with the reason, exactly as not covered and no session found already do — a fourth distinguishable silence rather than a crash.

Actual

aidd telemetry read --session <id>
  => OpencodeExportError: opencode export <id> failed: spawnSync opencode ETIMEDOUT
  => nothing stored, for any tool

Scope

  • Where a per-tool reader's failure is contained, and what the report prints for that tool.
  • Whether cli/.claude/rules/00-architecture/0-error-handling.md ("use-cases throw, no try/catch inside them") admits this case, or whether the containment belongs in the composition root instead. That rule already carves out a long-lived process; a fan-out over independent readers is a different shape and the decision should be explicit rather than assumed.
  • Not in scope: changing OpenCode's timeout, or reading OpenCode any other way.

Relations

Field Value
parent #631
related #629, #676

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    Status
    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions