Skip to content

fix(sessions): rare-event scan window and per-project hook paths (2.128.1) - #172

Merged
drknowhow merged 1 commit into
mainfrom
fix/liveness-scan-window-and-hook-paths
Sep 8, 2026
Merged

drknowhow merged 1 commit into
mainfrom
fix/liveness-scan-window-and-hook-paths

Conversation

@drknowhow

Copy link
Copy Markdown
Owner

Follow-up to #171. Both defects were found by shipping 2.128.0 to this machine and watching the runtime — neither had a failing test.

A busy activity log hid the running session

ProjectManager looked up the rare rows liveness depends on (session_start, session_save, session_end) with ActivityLog.get_recent(limit=1, event_type=...), which scans only the last limit * 100 lines. That window is minutes wide on a working project. Measured right after 2.128.0 went live, in this repo:

activity_log.jsonl: 20,825 lines, 4.9 MB
last session_start  -> 319 lines from EOF   (the session that was running)
get_recent(limit=1, event_type="session_start") -> []      6.4 ms

So the hub called an actively-served project idle, and the 20-minute idle rule was never even reached. This predates 2.128.0 by a long way and is the other half of "the live light is wrong".

  • ActivityLog.find_last(event_type, limit=1) scans the log backwards in 256 KB chunks, stitching lines across chunk boundaries, and stops at the first limit matches. It finds the row however much traffic sits in front of it, and it is faster than the call it replaces — 0.9 ms vs 6.4 ms on that same 4.9 MB log — because it stops instead of reading everything.
  • ProjectManager._rare_rows routes every rare-event lookup through it, falling back to get_recent when the object has no find_last: a test stub, or an older C3 in another checkout serving the same project. _get_last_session_timestamp now also considers session_end.

The hook migration wrote its own install path into a checkout

The hub runs from wherever C3 is installed, so _migrate_project_hooks built every command from its cli/ directory. Correct for the 59 projects that use the installed C3; wrong for this repo, whose other C3 hooks point at the working tree — the thing a developer actually edits. After #171 shipped, this project's fresh SessionStart entry pointed at site-packages/cli/hook_dispatch.py while its Stop entry pointed at the checkout: two sources of truth for one hook, and an edited hook would silently do nothing until the next install.

  • detect_project_cli_dir(path) reads the project's own settings, finds the cli/ directory its existing C3 hooks point at (a hook_*.py whose directory still holds hook_dispatch.py), and builds that project's entries against it. A path that no longer exists is ignored; a project with no C3 hooks yet falls back to the hub's own location. Each project gets its own table instead of one shared across all of them.

Verification

  • 11 new tests: tests/test_activity_log_find_last.py (buried row, newest-first + limit, chunk-boundary splits at 64/128/4096 bytes, corrupt line, missing file, parity with get_recent when both can see the row), migration cases for the detected / fallback / dead cli_dir, and a liveness case over a real 500-row log rather than a stub.
  • Full suite: 4022 passed, 7 skipped. ruff check . clean.
  • The one failure, tests/test_ci_act.py::TestActIntegration::test_runs_a_linux_job_and_reports_a_clean_failure (job.parser == 'unparsed', expected 'ruff'), reproduces on a clean main with my changes stashed and is unrelated to this PR. It is local-only — the class is skipUnless(ACT_READY), so GitHub CI never runs it. Filed separately.

https://claude.ai/code/session_01FLzc3usUhbfdF9M35UUMb5

…28.1)

Both found by shipping 2.128.0 and watching the runtime, not by a test.

A busy activity log hid the running session. ProjectManager looked up the
rare rows liveness depends on (session_start, session_save, session_end)
with ActivityLog.get_recent(limit=1, event_type=...), which scans only the
last limit*100 lines. That window is minutes wide on a working project:
right after 2.128.0 went live, this repo had its RUNNING session's
session_start sitting 319 lines from the end of a 20,825-line log and
get_recent returned [] — so the hub called an actively-served project idle
and the 20-minute rule was never even reached. It predates 2.128.0 by a
long way and is the other half of "the live light is wrong".

- ActivityLog.find_last(event_type, limit=1) scans backwards in 256 KB
  chunks, stitching lines across boundaries, and stops at the first match.
  It finds the row however much traffic is in front of it, and it is faster
  than the call it replaces (0.9 ms vs 6.4 ms on that 4.9 MB log) because
  it stops instead of reading the whole file.
- ProjectManager._rare_rows routes every rare-event lookup through it and
  falls back to get_recent when the object has no find_last — a test stub,
  or an older C3 in another checkout serving the same project.
  _get_last_session_timestamp now also considers session_end.

The hook migration wrote its own install path into a checkout. The hub runs
from wherever C3 is installed, so it built every command from its cli/
directory. Right for the 59 projects on the installed C3; wrong for this
repo, whose other hooks point at the working tree, where it would have made
an edited hook a no-op until the next install — two sources of truth for one
hook, invisible until something silently does not run.

- detect_project_cli_dir(path) reads the project's settings, finds the cli/
  its current C3 hooks use (a hook_*.py whose directory still holds
  hook_dispatch.py), and builds that project's entries against it. A path
  that no longer exists is ignored; a project with no C3 hooks falls back to
  the hub's own location. Each project gets its own table now.

Tests: tests/test_activity_log_find_last.py (backwards scan, chunk
boundaries, corrupt lines, missing file), migration cases for the detected
and fallback cli_dir, and a liveness test over a real 500-row log. Full
suite 4022 passed / 7 skipped; the one failure, tests/test_ci_act.py's act
integration case (parser 'unparsed' != 'ruff'), reproduces on clean main
and is unrelated — filed separately.

Claude-Session: https://claude.ai/code/session_01FLzc3usUhbfdF9M35UUMb5
@drknowhow
drknowhow merged commit 94f32e9 into main Sep 8, 2026
12 checks passed
@drknowhow
drknowhow deleted the fix/liveness-scan-window-and-hook-paths branch September 8, 2026 18:24
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.

1 participant