Known Issues
Everything here is real, reproduced, and open against v3.0.0. It is published rather than quietly tracked because most of these fail silently — you get a plausible wrong answer, not an error — and an hour spent debugging one of them is an hour this page could have saved.
The worst kind first: a tool that raises still completes the run successfully. If you are debugging an agent that seems to ignore its tools, read that entry before anything else.
Silent wrong answers
These produce no error. Nothing in the log says anything went wrong.
A tool that raises completes the run
An exception inside a tool produces no machine-readable signal at any layer. The run completes,
the caller gets a normal result, HTTP answers 200, and tool.call.completed.error is never
set. The only trace is that the model saw an error string and may or may not have mentioned it.
Until it is fixed: catch exceptions inside your own tools and return a string the model can act on. Do not rely on the run failing. → #250
A tool returning something unserializable reaches the model as a memory address
Return a value JSON cannot carry and it is neither rejected nor flagged — it is coerced to its
repr(), and that string, typically containing a raw memory address, enters both the event log
and the prompt.
Until it is fixed: return JSON-compatible values from tools. If you return an object today, check what the model is actually receiving. → #251, folded into #250
Agent(model=...) is ignored
Every run uses OPENAI_MODEL, whatever the agent declared. The parameter is accepted,
type-checked by the SDK, and then overridden by the run’s own configuration — so a cheap router
in front of an expensive specialist silently runs entirely on one model.
Until it is fixed: there is no per-agent model. One model per process. → #247
Cancelling a run that is waiting on a human does nothing
deck.cancel(run_id) against a run parked at interrupt() returns True and records the
signal. Nothing reads it. The run stays answerable, and answering it executes the rest of the
workflow for real. No event is written, so the log shows no trace that a cancel was asked for.
Until it is fixed: do not rely on cancel to stop a pending approval. Guard the post-approval nodes with your own check. → #229
usage.usd is always None
The cost field exists on every Usage and never carries a cost.
Until it is fixed: compute cost from input_tokens/output_tokens and your own price table.
→ #177
Defaults that will trap you
A default install cannot run a durable workflow
AGENTDECK_CHECKPOINT defaults to sqlite://.agentdeck/checkpoints.sqlite3, but the SQLite
checkpointer lives behind the [durability] extra. So durable = True — which every human
approval needs — fails on a plain install.
Until it is fixed: install agentdeck[durability] if you use durable = True.
→ #232
Approvals disappear on restart, under the shipped defaults
AGENTDECK_EVENTS defaults to memory:// while AGENTDECK_CHECKPOINT defaults to durable
SQLite. deck.pending() reads the event log; the timer path reads the checkpointer. After a
restart the two disagree, and a parked approval can never be answered because pending() returns
an empty list.
Until it is fixed: set AGENTDECK_EVENTS to a durable store — sqlite://, postgresql://
or redis:// — in anything that outlives one process. See
Choosing a Store Backend.
→ #212
A killed worker holds its session for an hour
A worker killed mid-turn leaves its session_id unusable for
AGENTDECK_RUNTIME_STALE_RUN_AFTER_SECONDS, which defaults to 3600. There is no way to
release it sooner.
Until it is fixed: lower the setting if your deployment restarts often. It trades against how long a healthy turn may go quiet. → #244
Provider compatibility
Handoffs fail against non-OpenAI endpoints
Against an OpenAI-compatible endpoint that is not OpenAI, a handoff returns a bare 400. On Gemini’s endpoint the message is:
400 - Please ensure that single turn requests end with a user role or the role field is empty.A transfer produces an assistant tool-call followed by a tool result, so the transferred-to agent’s request ends on a non-user role, which some providers reject. The handoff mechanism itself is fine — a bidirectional round trip works against a compliant endpoint.
Until it is fixed: run handoffs against OpenAI. Pointing one agent at a different provider is
not a workaround — see Agent(model=...) above.
→ #178
Rough edges
Not silent, not traps — just things that will cost you a few minutes.
| What | Issue |
|---|---|
agentdeck-serve --help crashes instead of printing usage | #245 |
run()’s return type makes the documented interrupt idiom fail a type checker | #231 |
Checkpointer connection failures surface as raw driver exceptions, not StoreError | #233 |
| An engine failure over HTTP returns bare text rather than the documented 500 shape | #243 |
answer() accepts any value with no validation, and the node owns interpreting it | #235 |
Omitting context on answer() silently gives the re-run node None | #255 |
| The CLI can send signals but cannot read anything — no inbox, no run view | #256 |
redis is a base dependency because the default session path imports it | #253 |
A DataBlock cannot be sent to a model, only received from one | #226 |
Deck.asgi() cannot serve an agent whose tools declare a Context | #227 |
What is being done about all this
Every entry above is on v3.1 — hardening, which exists for exactly this list. See the Roadmap.
Hit something that is not here? Open an issue . A reproduction is worth more than a diagnosis — most of this page came from people who sent one.