Skip to content

Persist app ERROR cause in status_message and expose via API (#129)#177

Open
ClaydeCode wants to merge 4 commits into
mainfrom
feature/clayde/persist-app-error-cause
Open

Persist app ERROR cause in status_message and expose via API (#129)#177
ClaydeCode wants to merge 4 commits into
mainfrom
feature/clayde/persist-app-error-cause

Conversation

@ClaydeCode

Copy link
Copy Markdown
Contributor

Closes #129.

What & why

update_app_status(name, status, message=...) only logged the error message — it was never persisted. Error reasons reached the UI solely through the transient on_app_install_error websocket event, so after a page reload an errored app showed a bare ERROR status with no cause.

This persists the cause in the database so it survives a reload and can be rendered by the web-terminal / Sundial.

Changes

  • Migration shard-core-0002-app-status-message.sql — adds a nullable status_message TEXT column to installed_apps. First migration after the init schema; establishes the shard-core-NNNN-<slug>.sql + -- depends: naming convention (now documented in agents.md).
  • update_app_status writes status_message when a status becomes ERROR and clears it (NULL) on any other transition. The direct update_status calls in app_tools.py (RUNNING/PAUSED/STOPPED/DOWN) also clear it via the new default-None param — which is exactly the "clear on leaving ERROR" behavior.
  • InstalledApp model gains status_message: Optional[str] = None, so it flows automatically into GET /protected/apps and GET /protected/apps/{name} (both use the InstalledAppWithMeta response model) and the websocket app push.
  • Tests (tests/test_app_status_message.py): persisted on ERROR; cleared on leaving ERROR; ignored (and actively cleared) for non-ERROR; and survives a reload via both the single-app and list API endpoints.

Acceptance criteria

  • ✅ Install failure → reload → error reason still visible via the API (covered by test_error_message_survives_reload_via_api, asserting both endpoints).
  • ✅ Migration applies cleanly on an existing DB — additive nullable column, -- depends: shard-core-0001-init, no table rewrite, no backfill. Verified: yoyo resolves the dependency chain and the migration runs on every test DB boot.

Recommended reading order

  1. migrations/shard-core-0002-app-status-message.sql
  2. shard_core/data_model/app_meta.py (model field)
  3. shard_core/database/installed_apps.py (update_status column write)
  4. shard_core/service/app_installation/util.py (update_app_status ERROR-guard)
  5. tests/test_app_status_message.py
  6. agents.md

Verification

  • tests/test_app_status_message.py — 4 passed. Proven falsifiable: forcing status_message = None in update_app_status makes test_error_message_is_persisted fail.
  • Regression suites run green: test_app_installation (12), plus test_model, test_db_snapshot, test_traefik_dyn_spec, test_app_error.
  • ruff check . clean; just cleanup applied.

Review panel

Five reviewer subagents ran (adversarial, test adversary, DevEx, DB/migration, API-contract), each given only the diff + issue. No blocking findings from any reviewer. Advisory findings addressed:

  • Test adversary — list endpoint untested (issue names it): fixed — the round-trip test now also asserts GET /protected/apps.
  • Test adversary — "ignore message" test was decorative for the clearing half: fixed — it now pre-seeds an ERROR message so it proves the STOPPED transition actively clears it.
  • Test adversary — API test on heavy api_client tier: fixed — demoted to the lighter app_client fixture (no Docker/lifespan).
  • Adversarial — ERROR+message durability across a core process restart (not browser reload): noted, not fixed. On shutdown, docker_shutdown_all_apps(force=True) flips apps with an existing compose file to DOWN, which clears the message. The persisted message inherits exactly the same restart-durability as the ERROR status itself; the issue's acceptance criterion is browser reload, which is fully satisfied. Broadening restart-survival is out of scope for Persist app error causes so the UI can show why an app is in ERROR #129.
  • DB/migration — new-snapshot→old-schema restore intolerance: noted, pre-existing property of the dynamic-column db_snapshot restore, only reachable on an unsupported version downgrade; migrate-runs-before-restore makes normal fresh restores always target the latest schema. Not introduced here.

🤖 Generated with Claude Code

ClaydeCode and others added 4 commits July 22, 2026 01:41
First migration after the init schema. Establishes the yoyo -- depends:
naming convention (shard-core-NNNN-<slug>). Column is nullable so it
applies cleanly to existing databases and defaults to no message.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
update_app_status now writes the message to the new status_message column
when a status becomes ERROR, and clears it (NULL) on any other transition.
The InstalledApp model gains a status_message field, so both
GET /protected/apps and GET /protected/apps/{name} return the reason,
which now survives a page reload instead of only reaching the UI via the
transient on_app_install_error websocket event.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Covers: ERROR message persisted, cleared on leaving ERROR, ignored for
non-ERROR status, and still returned by GET /protected/apps/{name} after
a reload. Documents the shard-core-NNNN-<slug> yoyo migration convention
in agents.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
- test_message_ignored_for_non_error_status now pre-seeds an ERROR message
  so it proves the STOPPED transition actively clears it, not just that a
  message was never set.
- API round-trip test now also asserts the list endpoint (GET /protected/apps),
  which the issue names explicitly, and uses the lighter app_client fixture
  (no Docker/lifespan) instead of api_client.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Persist app error causes so the UI can show why an app is in ERROR

1 participant