SessionStart hook runs procman cleanup (ADR-0219 slice 3) #714

Closed
opened 2026-09-25 15:01:11 +00:00 by jared · 4 comments
Owner

Problem

procman servers self-exit, but a server killed hard leaves a registry entry. ADR-0219 assigns the sweep to session start.

Approach

The os plugin's SessionStart hook is a Python check registry: plugins/os/hooks/session_start.py iterates checks.REGISTRY in plugins/os/hooks/checks.py, each entry a Check(name, fn, project_scoped, remediation) whose fn(ctx) returns OK, note(msg) (goes to additionalContext), or warn(msg) (goes to the systemMessage banner). The runner wraps every check in a 2-second SIGALRM budget. The cleanup becomes one more check. procman cleanup prints one removed <row> line per server it stopped and nothing otherwise, exit 0.

Required work:

  1. Add procman_cleanup(ctx) to plugins/os/hooks/checks.py: resolve the executable with shutil.which("procman"); absent -> warn("procman not on PATH; helper-server cleanup skipped"); present -> subprocess.run([exe, "cleanup"], capture_output=True, text=True, timeout=1.5); stdout non-empty -> note("procman cleanup: " + the removed lines joined by "; "); stdout empty -> OK; subprocess.TimeoutExpired -> warn("procman cleanup exceeded 1.5s").
  2. Register it in REGISTRY as Check("procman-cleanup", procman_cleanup, project_scoped=False, remediation="put the os-sdlc plugin bin directory on PATH so procman resolves").
  3. Tests in plugins/os/tests/hook_test.py, run with python3 plugins/os/tests/hook_test.py, patching shutil.which and subprocess.run: absent -> warn; present with empty stdout -> OK; present with removed x stdout -> note containing removed x; TimeoutExpired -> warn. One real test behind PROCMAN_REAL=1 that starts a procman-managed server in a tmpdir, deletes the tmpdir, runs the check, and asserts the note names it.
  4. Add procman-cleanup to the check list in plugins/os/CLAUDE.md and add one History row to the os implementation-status leaf.

Out of scope: wiring the Python hook tests into the os target's test_command (Ruby-only today) is a separate ticket.

Acceptance criteria

  1. With procman on PATH and a registry entry whose root directory was deleted, a new session start removes the entry and additionalContext contains one line starting procman cleanup: that names it.
  2. With nothing to remove, the hook adds no additionalContext and no banner line for this check.
  3. With procman absent from PATH, the banner carries one warn line for this check and the other checks still run.
  4. python3 plugins/os/tests/hook_test.py passes, and RemediationTest.test_every_registry_entry_has_a_remediation still passes.

Owner sketch

  • checks.procman_cleanup owns resolution, the subprocess call, and mapping stdout to a CheckResult; it takes no new Ctx fields.
  • checks.REGISTRY owns registration; session_start.py is unchanged.
  • The procman gem owns what "removed" means; the check only relays its lines.

Blocked by: #712 (closed), #713 (closed).

VERDICT: CREATE — planned slice 3 of accepted ADR-0219 with recorded provenance and concrete acceptance criteria; blockers shipped.

Origin

Session: ecfe6e11-0810-4431-88e5-8e24d34a239e
Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/ecfe6e11-0810-4431-88e5-8e24d34a239e.jsonl
Where: plugins/os/hooks
ADR: 0219

## Problem procman servers self-exit, but a server killed hard leaves a registry entry. ADR-0219 assigns the sweep to session start. ## Approach The os plugin's SessionStart hook is a Python check registry: `plugins/os/hooks/session_start.py` iterates `checks.REGISTRY` in `plugins/os/hooks/checks.py`, each entry a `Check(name, fn, project_scoped, remediation)` whose `fn(ctx)` returns `OK`, `note(msg)` (goes to additionalContext), or `warn(msg)` (goes to the systemMessage banner). The runner wraps every check in a 2-second SIGALRM budget. The cleanup becomes one more check. `procman cleanup` prints one `removed <row>` line per server it stopped and nothing otherwise, exit 0. Required work: 1. Add `procman_cleanup(ctx)` to `plugins/os/hooks/checks.py`: resolve the executable with `shutil.which("procman")`; absent -> `warn("procman not on PATH; helper-server cleanup skipped")`; present -> `subprocess.run([exe, "cleanup"], capture_output=True, text=True, timeout=1.5)`; stdout non-empty -> `note("procman cleanup: " + the removed lines joined by "; ")`; stdout empty -> `OK`; `subprocess.TimeoutExpired` -> `warn("procman cleanup exceeded 1.5s")`. 2. Register it in `REGISTRY` as `Check("procman-cleanup", procman_cleanup, project_scoped=False, remediation="put the os-sdlc plugin bin directory on PATH so procman resolves")`. 3. Tests in `plugins/os/tests/hook_test.py`, run with `python3 plugins/os/tests/hook_test.py`, patching `shutil.which` and `subprocess.run`: absent -> warn; present with empty stdout -> OK; present with `removed x` stdout -> note containing `removed x`; TimeoutExpired -> warn. One real test behind `PROCMAN_REAL=1` that starts a procman-managed server in a tmpdir, deletes the tmpdir, runs the check, and asserts the note names it. 4. Add `procman-cleanup` to the check list in `plugins/os/CLAUDE.md` and add one History row to the os implementation-status leaf. Out of scope: wiring the Python hook tests into the os target's `test_command` (Ruby-only today) is a separate ticket. ## Acceptance criteria 1. With `procman` on PATH and a registry entry whose root directory was deleted, a new session start removes the entry and additionalContext contains one line starting `procman cleanup:` that names it. 2. With nothing to remove, the hook adds no additionalContext and no banner line for this check. 3. With `procman` absent from PATH, the banner carries one warn line for this check and the other checks still run. 4. `python3 plugins/os/tests/hook_test.py` passes, and `RemediationTest.test_every_registry_entry_has_a_remediation` still passes. ## Owner sketch - `checks.procman_cleanup` owns resolution, the subprocess call, and mapping stdout to a CheckResult; it takes no new Ctx fields. - `checks.REGISTRY` owns registration; `session_start.py` is unchanged. - The procman gem owns what "removed" means; the check only relays its lines. Blocked by: #712 (closed), #713 (closed). VERDICT: CREATE — planned slice 3 of accepted ADR-0219 with recorded provenance and concrete acceptance criteria; blockers shipped. ## Origin Session: ecfe6e11-0810-4431-88e5-8e24d34a239e Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/ecfe6e11-0810-4431-88e5-8e24d34a239e.jsonl Where: plugins/os/hooks ADR: 0219
Author
Owner

Blocked until procman 0.2.0 ships. See procman #4 (Release procman 0.2.0 as the stable line for cc-os): jared/procman#4.

Scope unchanged.

Blocked until procman 0.2.0 ships. See procman #4 (Release procman 0.2.0 as the stable line for cc-os): https://forgejo.swansoncloud.com/jared/procman/issues/4. Scope unchanged.
Author
Owner

procman v0.2.0 is tagged on main (3c5f3b1); the blocker on this ticket is cleared. For the SessionStart hook: the CLI is exe/procman; procman cleanup sweeps servers whose root is gone or whose supervisor is dead and purges dead leases; procman list and procman stop --all are available. A missing procman binary is the only precondition to check.

procman v0.2.0 is tagged on main (3c5f3b1); the blocker on this ticket is cleared. For the SessionStart hook: the CLI is exe/procman; procman cleanup sweeps servers whose root is gone or whose supervisor is dead and purges dead leases; procman list and procman stop --all are available. A missing procman binary is the only precondition to check.
Author
Owner

Work started on branch ticket-714 (worktree). Hand-finished outside the os-sdlc pipeline: the os target's test_command runs Ruby only, so the pipeline gates cannot see the Python hook tests.

Work started on branch ticket-714 (worktree). Hand-finished outside the os-sdlc pipeline: the os target's test_command runs Ruby only, so the pipeline gates cannot see the Python hook tests.
Author
Owner

Resolution

Done: SessionStart check procman-cleanup in plugins/os/hooks/checks.py runs procman cleanup, relays removed servers to additionalContext, warns when procman is absent or exceeds 1.5s; registered with remediation; CLAUDE.md check list updated.

Evidence: Branch ticket-714 commit 8ebbffc9; python3 plugins/os/tests/hook_test.py: 85 tests OK (1 skipped); PROCMAN_REAL=1 run: 85 tests OK including the live removed-server test; ruby plugins/os/tests/all.rb 27 runs 0 failures; session_start.py prints no procman line when nothing was removed.

Follow-ups: #738 filed: the os target's gate runs Ruby tests only, so the Python hook tests never run under os-sdlc gates; this ticket was hand-finished for that reason. procman ensure for lsp-probe and unix-socket kinds did not work standalone during the live test; only rubocop with a Gemfile did; noted on procman, no cc-os ticket.

## Resolution **Done:** SessionStart check procman-cleanup in plugins/os/hooks/checks.py runs procman cleanup, relays removed servers to additionalContext, warns when procman is absent or exceeds 1.5s; registered with remediation; CLAUDE.md check list updated. **Evidence:** Branch ticket-714 commit 8ebbffc9; python3 plugins/os/tests/hook_test.py: 85 tests OK (1 skipped); PROCMAN_REAL=1 run: 85 tests OK including the live removed-server test; ruby plugins/os/tests/all.rb 27 runs 0 failures; session_start.py prints no procman line when nothing was removed. **Follow-ups:** #738 filed: the os target's gate runs Ruby tests only, so the Python hook tests never run under os-sdlc gates; this ticket was hand-finished for that reason. procman ensure for lsp-probe and unix-socket kinds did not work standalone during the live test; only rubocop with a Gemfile did; noted on procman, no cc-os ticket.
jared closed this issue 2026-09-25 21:49:42 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
jared/cc-os#714
No description provided.