Skip to content

AUDIT-08 — Medium-High — Persistence errors are swallowed; phantom successes and stale running #65

Closed

Description

@getdownandcode

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-155 and :163-172
  • agentkit/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_run fails → the API returns HTTP 200 succeeded for a run that was never persisted; GET /runs/{run_id} then returns 404.
  • update_run fails → the stored record stays status="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_messages is logged (ERROR, with run_id / session_id) — never silently suppressed.
  • POST /runs does not return HTTP 200 when the run could not be persisted: it returns a 5xx (or an explicit non-success status with failure_reason set), and never a succeeded status 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_run failed is not left in running (either updated, or explicitly marked failed/errored).
  • The RunResponse returned by POST /runs always matches the persisted record (status, answer, goal, session id, token counts).
  • Regression tests: inject a store whose create_run / update_run raises 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 . and mypy agentkit clean.
  • Docstrings updated where behaviour changed; docs/PROGRESS.md updated (status, PR link, commit count, notes).
  • A new ADR in docs/DECISIONS.md if 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

  1. getdownandcode commented on Oct 20, 2026

    @getdownandcode
    OwnerAuthor

    Audit index (18 separate issues, fix one by one)

    ID Issue Severity
    AUDIT-01 #58 Critical
    AUDIT-02 #59 Critical
    AUDIT-03 #60 High
    AUDIT-04 #61 High
    AUDIT-05 #62 High
    AUDIT-06 #63 High/Medium
    AUDIT-07 #64 Medium-High
    AUDIT-08 #65 Medium-High
    AUDIT-09 #66 Medium-High
    AUDIT-10 #67 Medium-High
    AUDIT-11 #68 Medium
    AUDIT-12 #69 Medium
    AUDIT-13 #70 Medium
    AUDIT-14 #71 Medium
    AUDIT-15 #72 Medium
    AUDIT-16 #73 Medium
    AUDIT-17 #74 Low
    AUDIT-18 #75 Low/Deployment

    Each issue is self-contained: current code, reproduction, pass cases, suggested branch and PR title. Reference the relevant one with Fixes #NN in your PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions