You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(cost): close runs rows on completion (#537) (#538)
* fix(cost): close runs rows on completion (#537)
What changed
------------
- ``CostStore.close_run`` stamps ``ended_at`` plus task/agent counters
with a COALESCE-guarded UPDATE (live values win, NULL preserves
existing).
- ``CostStore.close_open_runs_for_project`` bulk-closes every open run
for a project; called from ``end_experiment`` after ``monitor.stop()``.
- ``CostAggregator.run_audit`` reports ``run_open``; ``project_audit``
reports ``runs_total`` and ``runs_open`` so the audit can detect the
"every run open since insertion" failure mode.
- ``scripts/backfill_run_close_state.py`` walks every NULL row and
derives ``ended_at`` from ``MAX(token_events.timestamp) WHERE
run_id = ?`` — best on-disk approximation for unattended close.
Why
---
The ``runs`` table has lifecycle columns the schema anticipates, but
nothing in the Python code ever wrote them after #522. Every row has
been "open" since insertion, blocking dashboard queries on
``ended_at IS NOT NULL`` and any wall-clock-time analysis (run
duration, cost-per-minute, coordination-tax rate). The token-audit
work in #528 confirmed token attribution but never asserted lifecycle
closure — so the gap stayed silent.
Real-data validation
--------------------
Ran backfill on ``~/.marcus/costs.db``:
$ python scripts/backfill_run_close_state.py
Closed 1 runs.
$ sqlite3 ~/.marcus/costs.db \
"SELECT COUNT(*), SUM(ended_at IS NULL) FROM runs;"
1|0
Test plan
---------
- [x] ``pytest tests/unit/cost_tracking/`` — 95 passed (11 new)
- [x] ``mypy src/cost_tracking/cost_store.py
src/cost_tracking/cost_aggregator.py`` clean
- [x] Backfill script ran end-to-end against real costs.db
Related
-------
- Closes#537
- Follows #522 (which added ``record_run`` without a close path) and
#528 (which audited tokens but not lifecycle)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(cost): canonicalize project_id in close_open_runs_for_project (#537)
``record_run`` stored the dashless UUID form via
``canonical_project_id``, but ``end_experiment`` passed
``monitor.project_id`` straight through. When the monitor was handed
the dashed UUID form (ProjectRegistry path), the live close lookup
matched zero rows and silently closed nothing — leaving the
backfill script as the only safety net.
Normalize at the close-helper entry: ``canonical_project_id(...)``
or fall back to the original string for non-UUID inputs (matches
the rest of the cost layer, which normalizes on the way in).
Regression test asserts a dashed input still closes the dashless
row in ``runs``. Caught by Kaia on PR #538 review.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* comment to not fix code
* fix(cost): close only latest open run per project on end_experiment (#537)
The cost schema allows multiple ``runs`` rows per ``project_id``
(retries, repeated traversals — ``run_id`` is the PK, project views
group/list runs). The bulk close in
``close_open_runs_for_project`` stamped every historical open row
for the project with the just-finished experiment's ``ended_at``
and final counters, corrupting older runs.
Replace with ``close_latest_open_run_for_project``: scope to the
``MAX(started_at)`` open run only. Older open rows are left to the
periodic ``backfill_run_close_state.py`` script, which derives
``ended_at`` from each row's own ``MAX(token_events.timestamp)`` —
the on-disk approximation that's correct per-run rather than a
batch-overwrite from the live hook.
Return type tightened from ``int`` (count) to ``Optional[str]``
(the closed run_id) so callers can log which row was touched.
Caught by Codex P2 on PR #538.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
0 commit comments