Repository navigation
fix(hooks): spool captures for a detached drainer where the resident worker cannot run (#659) - #663
Conversation
…t run (#659) The worker needs AF_UNIX, fcntl, geteuid and Popen(pass_fds): none exist on Windows, so every capture raised OSError and was reported as capture_skipped. A capability test (capture_peer.is_supported) now selects the path before any I/O; unsupported platforms run capture_store.remember, the same function the worker awaits, once per hook process. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LFAg4i6CQpZrNXb8cHH8ax
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LFAg4i6CQpZrNXb8cHH8ax
…worker cannot run (#659) The first fix stored in the hook process on Windows, which puts the sentence_transformers import (3.61 s measured, 4.4 s warm and 10.5 s cold for the whole capture) on the synchronous path of a hook with timeout 10. The hook now validates the payload, writes it atomically to .capture-worker/spool and starts capture_drain detached (DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP on Windows) without waiting. The drainer takes a non-blocking kernel lock, stores every pending file through capture_store.store with one handler load per burst, deletes after storing and rescans once after releasing the lock. A refused or failing file is renamed *.rejected and reported as capture_skipped. POSIX keeps the resident worker. The three capture test files join the Windows CI leg. ADR-1094. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LFAg4i6CQpZrNXb8cHH8ax
…pool (#659) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LFAg4i6CQpZrNXb8cHH8ax
…on (#659) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LFAg4i6CQpZrNXb8cHH8ax
|
Revue du contrôleur (Opus 5.5), quatrième passage PR #663 (#659), 4e passage : APPROVELes deux bloquants de 940a6d3 sont corrigés. Bloquants précédents, corrigés
CITout est vert, Non bloquant (issues pour le lead)
Preuves
|
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LFAg4i6CQpZrNXb8cHH8ax
|
ZETETIC-REVIEW: APPROVE Tête fusionnée: 0c663b9 = 1aadf34 (approuvée par le contrôleur Opus, 4e passage, détail ci-dessous) + merge de origin/main 306fafe (#668). Vérifié par le lead: le diff de la PR contre sa base est identique avant et après la fusion hors wiki/manifest.json (15 fichiers, 1239 insertions, 32 suppressions, mêmes fichiers); le manifeste garde ADR-1093 (#668) et ADR-1094 (cette PR). CI: 24 vérifications réussies, 4 ignorées, Windows compris, sur 0c663b9. Non vérifié: tout le natif Windows hors ce que le job CI exécute. PR #663 (#659), 4e passage : APPROVELes deux bloquants de 940a6d3 sont corrigés. Bloquants précédents, corrigés
CITout est vert, Non bloquant (issues pour le lead)
Preuves
|
Fixes #659 for the capture path (auto-capture stored nothing on Windows). The other half of #659's text,
check_setupansweringready: truewhile every capture fails, is not covered here: it is tracked by #660.Root cause
Since 17370a6 every capture goes through a resident worker that needs
AF_UNIX,fcntl,geteuidandPopen(pass_fds). None exist on Windows;capture_peer.supported()raised anddispatch()reportedcapture_skippedfor every event.The first version of this PR stored in the hook process. Measured 2026-10-07 (macOS, isolated SQLite, warm):
sentence_transformersimport 3.61 s, whole capture 4.4 s warm and 10.5 s cold, against the hook'stimeout: 10. That puts the cost the worker exists to amortize (ADR-0486/0508) back on the synchronous path, so the host would abort the hook instead of the capture being refused.Fix (ADR-1094)
Where
capture_peer.is_supported()is false, the hook never loads the handler:capture_dispatch.dispatchvalidates the payload, writes it unchanged and atomically to<CLAUDE_DIR>/.capture-worker/spool/(infrastructure/capture_spool.py) and startspython -m mcp_server.hooks.capture_drainwithout waiting. Hook body: 0.06 s measured.popen_options(platform, stream)is a pure function: WindowsDETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP,close_fds, nopass_fds, nostart_new_session; other platformsstart_new_session. stdout/stderr go todrain.err.capture_drain:wire_composition_root()([audit] 17 core/ modules import os or pathlib (core must be zero-I/O) #560),close_shared_store_on_exit(fix(hooks): consolidate_background never exits after a successful cycle — unclosed psycopg pool spins a core at 98% CPU indefinitely #398), non-blocking kernel lock (msvcrt.locking/flock), stores every pending file through the samecapture_store.storethe worker awaits (one handler load per burst), deletes after storing (at-least-once, the write gate absorbs a replay), releases, rescans once. A refused or failing file becomes*.rejectedand acapture_skippedreport.resident_worker_available()andstore_in_processare removed.ci.yml: the three capture test files join the Windows leg (ADR-1091 convention), whereis_supported()is false natively, themsvcrtlock is real and the detached child runs with the real flags.docs/provenance/capture-worker-design.md: false "no Windows runner" claim removed; spool described.Completion Ledger
_load_rememberraises)ci.ymltelemetry.jsonlwithremembersamples and nocapture_skipped, spool emptycheck_setup"ready" while capture is brokenTests
test_capture_unsupported_platform.py(hook path,popen_options, capability),test_capture_drain.py(real handler, held lock, reject, handler failure, rescan, drainer outliving its parent),test_capture_spool.py. The hook behaviour is red on origin/main (nothing spooled,capture_skipped) and on baa8ece (handler loaded in the hook).🤖 Generated with Claude Code
https://claude.ai/code/session_01LFAg4i6CQpZrNXb8cHH8ax