Repository navigation
Description
Summary
Severity: Medium-High
Area: Persistence / API
Affected files: agentkit/core/agent.py:149-155, :163-172, agentkit/api/routes.py:61-89
Suggested branch: fix/persistence-error-handling
Suggested PR title: fix(persistence): fail loudly instead of returning phantom successes
This is finding AUDIT-08 of 18 from a consolidated audit of agentkit/. It is deliberately self-contained so it can be fixed on its own branch with its own PR.
Related findings: Related: AUDIT-03 (placeholder run row), AUDIT-09 (session_id length), AUDIT-10 (history load).
AUDIT-08 — Medium-High — Persistence errors are swallowed; phantom successes and stale running
Files
agentkit/core/agent.py:149-155and:163-172agentkit/api/routes.py:61-89
Current code — agentkit/core/agent.py:149-155 and :163-172
if self.memory is not None and hasattr(self.memory, "create_run"):
with contextlib.suppress(Exception):
await self.memory.create_run(
run_id=run_id,
goal=goal,
session_id=session_id,
) if self.memory is not None and hasattr(self.memory, "update_run"):
with contextlib.suppress(Exception):
await self.memory.update_run(
run_id=result.run_id,
status=result.status,
...
)Current code — agentkit/api/routes.py:61-89
if hasattr(memory_store, "get_run"):
run_record = await memory_store.get_run(result.run_id)
if run_record is not None:
return RunResponse(
run_id=run_record.id,
...
)
now = datetime.now(UTC)
return RunResponse(
run_id=result.run_id,
session_id=payload.session_id,
goal=payload.goal,
status=result.status.value,
...Why this is a bug
Every persistence call is wrapped in contextlib.suppress(Exception) with no logging, and the route falls back to synthesising a RunResponse from the in-memory result. Consequences, both reproduced:
create_runfails → the API returns HTTP 200succeededfor a run that was never persisted;GET /runs/{run_id}then returns 404.update_runfails → the stored record staysstatus="running"forever and the final answer is lost from the API response.
Reproduced
create_run raises -> POST /runs => HTTP 200 {"status": "succeeded", "answer": "done"} (no DB row)
update_run raises -> POST /runs => HTTP 200 {"status": "running", "answer": null} (stale forever)
Pass cases
- A failure in
create_run/update_run/save_messagesis logged (ERROR, withrun_id/session_id) — never silently suppressed. -
POST /runsdoes not return HTTP 200 when the run could not be persisted: it returns a 5xx (or an explicit non-success status withfailure_reasonset), and never asucceededstatus for a non-existent run. -
GET /runs/{run_id}for a run that was never persisted returns 404 (no phantom record) — and the API response for a persisted run always reflects the final status. - A run whose
update_runfailed is not left inrunning(either updated, or explicitly marked failed/errored). - The
RunResponsereturned byPOST /runsalways matches the persisted record (status, answer, goal, session id, token counts). - Regression tests: inject a store whose
create_run/update_runraises and assert the HTTP status, the log record, and the stored state.
Definition of done (per AGENTS.md)
- New/updated unit and integration tests covering the pass cases above, all passing (
pytest). -
ruff check .,ruff format --check .andmypy agentkitclean. - Docstrings updated where behaviour changed;
docs/PROGRESS.mdupdated (status, PR link, commit count, notes). - A new ADR in
docs/DECISIONS.mdif an architectural decision was made. - Branch pushed and PR opened (never commit directly to
main).
Baseline at the time of writing: pytest is green on main (all tests pass; tests/real/* skipped unless AGENTKIT_REAL_INFRA=1).
Activity
getdownandcode commentedon Oct 20, 2026
Audit index (18 separate issues, fix one by one)
Each issue is self-contained: current code, reproduction, pass cases, suggested branch and PR title. Reference the relevant one with
Fixes #NNin your PR.