Repository navigation
fix(sessions): rare-event scan window and per-project hook paths (2.128.1) - #172
Merged
Merged
Conversation
…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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
ProjectManagerlooked up the rare rows liveness depends on (session_start,session_save,session_end) withActivityLog.get_recent(limit=1, event_type=...), which scans only the lastlimit * 100lines. That window is minutes wide on a working project. Measured right after 2.128.0 went live, in this repo: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 firstlimitmatches. 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_rowsroutes every rare-event lookup through it, falling back toget_recentwhen the object has nofind_last: a test stub, or an older C3 in another checkout serving the same project._get_last_session_timestampnow also considerssession_end.The hook migration wrote its own install path into a checkout
The hub runs from wherever C3 is installed, so
_migrate_project_hooksbuilt every command from itscli/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 freshSessionStartentry pointed atsite-packages/cli/hook_dispatch.pywhile itsStopentry 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 thecli/directory its existing C3 hooks point at (ahook_*.pywhose directory still holdshook_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
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 withget_recentwhen both can see the row), migration cases for the detected / fallback / deadcli_dir, and a liveness case over a real 500-row log rather than a stub.ruff check .clean.tests/test_ci_act.py::TestActIntegration::test_runs_a_linux_job_and_reports_a_clean_failure(job.parser == 'unparsed', expected'ruff'), reproduces on a cleanmainwith my changes stashed and is unrelated to this PR. It is local-only — the class isskipUnless(ACT_READY), so GitHub CI never runs it. Filed separately.https://claude.ai/code/session_01FLzc3usUhbfdF9M35UUMb5