Repository navigation
fix(kanban): coerce foreign timestamp values when reading task/run/event rows - #97462
liuhao1024 wants to merge 1 commit into
Conversation
…ent rows SQLite's type affinity lets raw SQL store ISO-8601 strings into the INTEGER timestamp columns silently. Every mutator in kanban_db writes int(time.time()), but rows written outside the mutators (e.g. a worker shell running raw SQL) leave ISO strings behind, and Run.from_row's int(row["ended_at"]) then crashes every later read of the affected card with `invalid literal for int() with base 10` — kanban show (text and JSON), the kanban_show worker context, and the dashboard's list_runs all fail (NousResearch#97455). Readers now tolerate those strays via _coerce_epoch: numeric values pass through, timezone-aware ISO-8601 strings convert to epoch seconds, and anything untrustworthy (naive ISO, garbage) degrades to None so the card stays readable instead of crashing. Also covers the worker-context "recent work" renderer and the dispatcher respawn/cooldown reads.
|
The Windows-only failure in this run is a known flaky timing test, unrelated to this diff. Failing test: Why it's unrelated to this change:
No rerun attempted since external contributors can't trigger one; happy to rebase if that's preferred. |
|
Verified this covers the issue completely. Reproduced the crash independently: |
|
Thanks for reproducing the crash independently and confirming the reader-side coercion covers the issue — matching the reporter's intent with a defensive read path was exactly the goal. |
What does this PR do?
kanban show(text and JSON), thekanban_showworker context, and the dashboard's run history all crash withinvalid literal for int() with base 10: '2026-08-28T14:35:22+00:00'once a timestamp column intasks/task_runs/task_eventsholds an ISO-8601 string, making the affected card unreadable (#97455).SQLite's type affinity accepts those writes silently, and every mutator in
kanban_dbalready writesint(time.time())— the stray strings come from rows written outside the mutators (e.g. a worker shell running raw SQL). Since the writer can't be pinned down, the fix is on the reader side: a new_coerce_epochhelper passes numeric values through, converts timezone-aware ISO-8601 strings (both+00:00andZspellings observed in the wild) to epoch seconds, and degrades anything untrustworthy (naive ISO, garbage) toNoneso the card stays readable. Naive ISO strings are deliberately dropped rather than guessed at a timezone, so a malformed value is never rendered as a wrong time.Applied at the row mappers (
Task.from_row,Run.from_row, bothEventconstructors), which covers every consumer of those objects, plus the two raw-SQL reader spots in the dispatcher (respawn guard, rate-limit cooldown) and the worker-context "recent work" renderer that calledint()directly.Related Issue
Fixes #97455
Type of Change
Changes Made
hermes_cli/kanban_db.py: added_coerce_epoch()— epoch/float/epoch-as-str pass through, timezone-aware ISO-8601 converts to epoch seconds, naive ISO and garbage degrade toNonehermes_cli/kanban_db.py:Task.from_row/Run.from_row/ bothEventconstructions now coercecreated_at/started_at/completed_at/ended_at/claim_expires/last_heartbeat_atinstead of passing strays through or calling bareint()hermes_cli/kanban_db.py:check_respawn_guard(rate-limit cooldown + recent-completion window) and thebuild_worker_context"recent work" renderer coerce rawended_atvalues instead ofint()tests/hermes_cli/test_kanban_db.py:_coerce_epochmatrix test + row-mapper regression that seeds the exact corruption from the issuetests/hermes_cli/test_kanban_cli.py: end-to-endkanban showregression (text + JSON) reproducing the issue'sUPDATEscenarioHow to Test
uv run --extra dev python -m pytest tests/hermes_cli/test_kanban_db.py tests/hermes_cli/test_kanban_cli.py tests/tools/test_kanban_tools.py -q— Observed result:70 passed, 1 skipped(the skip is a pre-existing environment-conditional test, unrelated)uv run --extra dev python -m pytest tests/hermes_cli/test_kanban_db.py::test_readers_coerce_iso_strings_in_integer_timestamp_columns tests/hermes_cli/test_kanban_cli.py::test_show_survives_iso_strings_in_integer_timestamp_columns -q— Observed result:2 passed; the tests store'2026-08-28T14:35:22+00:00'intotask_runs.ended_atand'2026-08-28T16:58:56.613Z'intotasks.completed_at/task_events.created_atexactly as in the issue, then assertshowrenders and the JSON payload carries epoch intsUPDATE task_runs SET ended_at = '2026-08-28T14:35:22+00:00' ...thenhermes kanban show <task_id>): should pass — the card renders andruns[].ended_atis the coerced epoch int1756384522instead of raisinginvalid literal for int()Checklist
Code
fix(scope):,feat(scope):, etc.)_fmt_tsrenderer — this crash happens earlier, at theRun.from_rowdata layer, which fix(kanban): tolerate string timestamps in _fmt_ts #86584 does not touch)pytest tests/ -qand all tests passDocumentation & Housekeeping
docs/, docstrings) — or N/A (behavior rationale documented in the_coerce_epochdocstring)cli-config.yaml.exampleif I added/changed config keys — or N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — or N/A