Skip to content

fix: harden fail-soft read/import paths against malformed local data (#222-#226) - #227

Open
mikehasa wants to merge 3 commits into
mainfrom
fix/fail-soft-hardening-222-226
Open

mikehasa wants to merge 3 commits into
mainfrom
fix/fail-soft-hardening-222-226

Conversation

@mikehasa

Copy link
Copy Markdown
Owner

Summary

Five fail-soft / evidence-integrity gaps reported as #222#226, each verified by reproducing the exact failure against main before fixing. In every case one malformed value in the user's own client logs or event ledger — or a session-id collision across clients — could abort a whole read/import or misattribute evidence, instead of skipping the affected row. All fixes are minimal and additive, preserve byte-identical behavior for normal inputs, and ship with a regression test.

Fixes

  • bug: malformed Claude plan-usage org values can abort the entire limits read #222 rate_limitsread_claude_plan_usage_latest() used the raw org as a dict key before normalizing, so a sample with a list/dict org raised TypeError: unhashable type and dropped every org's snapshot. Now such samples are skipped, mirroring the existing non-Mapping skip.
  • bug: non-finite timestamps from client logs can crash usage import #223 client_usage_timestamp_seconds() / _optional_float() had no finite check; a NaN/Infinity token (which json.loads accepts) crashed the parser, and the exception escaped the top-level per-client except (read/OS/sqlite only), aborting the entire usage refresh. Both now treat non-finite as absent, matching the existing _cursor_timestamp_seconds guard.
  • bug: mechanical check linking ignores client when matching session IDs #224 api_link_mechanical_checks_by_session_time() keyed check→step linking on client_session_id alone, so within one explicitly-merged cross-client Task two clients reusing a raw id (e.g. an MCP default) could credit one client's passing hook check to the other's step, falsely lifting it to independently_checked. Now keyed by (client, session id) on both sides.
  • bug: left-behind detection conflates same session IDs across clients #225 task_outcome / receipt — the left-behind inference keyed session identity on the raw id string while the rest of the ledger uses (client, client_session_id); two clients reusing an id suppressed the inactive/mostly-done downgrade. All three functions now key on the (client, session id) pair.
  • bug: SQLite event-log indexing crashes on oversized created_at values #226 event_log_extract_columns() did a bare float(created_at); an out-of-range integer (e.g. 10**400) raised OverflowError (not caught by the json.loads guard) and one poison line aborted reconcile_from_file() / store open. A small coercion now catches overflow and rejects non-finite, keeping the raw line mirrored verbatim with an empty index column.

Testing

  • New / updated regression tests in test_rate_limits, test_client_usage, test_mechanical_capture, test_task_outcome, test_event_log.
  • One under-specified fixture in test_opencode_actions was corrected to carry the client a real opencode projection stamps (the check now attaches only within the same client).
  • Full suite: 2891 passed (PYTHONPATH=src .venv/bin/python -m pytest -q).

Fixes #222
Fixes #223
Fixes #224
Fixes #225
Fixes #226

Five robustness gaps where one malformed value in the user's own client
logs or ledger could abort a whole read/import (or misattribute evidence)
instead of skipping the affected row. Each keeps byte-identical behavior for
normal inputs and adds a regression test.

- rate_limits: skip a plan-usage sample whose `org` is unhashable (list/dict)
  rather than aborting every org's snapshot. (#222)
- client_usage: guard `_timestamp_seconds`/`_optional_float` against
  non-finite (NaN/Infinity) values so one bad log line no longer aborts the
  whole usage refresh, matching the existing cursor-usage guard. (#223)
- api: key mechanical-check session linking by (client, session id) so a
  passing hook check is not credited across clients in a merged Task and a
  step is not falsely marked independently checked. (#224)
- task_outcome/receipt: key left-behind session identity by (client, session
  id) so two clients reusing one session-id string no longer suppress the
  inactive / mostly-done downgrade. (#225)
- event_log: coerce `created_at` fail-soft (catch OverflowError, reject
  non-finite) so an oversized timestamp is mirrored verbatim with an empty
  index column instead of aborting reconcile / store open. (#226)
mikehasa and others added 2 commits September 15, 2026 00:15
# Conflicts:
#	CHANGELOG.md
#	src/agentacct/event_log.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment