Skip to content

feat(wiki): safe headless wiki-authoring drain — async, throttled, anti-fabrication - #70

Merged
cdeust merged 2 commits into
mainfrom
fix/headless-authoring-drain-safety
Jun 30, 2026
Merged

feat(wiki): safe headless wiki-authoring drain — async, throttled, anti-fabrication#70
cdeust merged 2 commits into
mainfrom
fix/headless-authoring-drain-safety

Conversation

@cdeust

@cdeust cdeust commented Jun 30, 2026

Copy link
Copy Markdown
Owner

What

Makes the headless wiki-authoring drain safe to enable end-to-end.

Feature

  • Async claude -p invocation (asyncio.create_subprocess_exec + wait_for) — no longer blocks the event loop.
  • Per-cycle budget: concurrency / wall-clock / USD caps via CORTEX_HEADLESS_* env knobs (defaults 4 / 300s / $5).
  • Anti-fabrication: Scope.groundable filter — non-derivable scopes (prd/decisions/changelog/roadmap/accessibility/localization) are never authored from scratch.

Reviewer fixes (code-reviewer REQUEST CHANGES → resolved)

  • BLOCK-1_claude_invoke kills the subprocess in a finally when returncode is None. CancelledError (a BaseException) escaped the except clauses and leaked a zombie; the timeout path now shares the same kill. New test: test_cancellation_kills_subprocess.
  • BLOCK-3_write_anchor_page mkdir moved inside the try/except OSError, so a PermissionError can't propagate through gather(return_exceptions=False) and abort the anchor phase.
  • BLOCK-2 — split the 1766-line module into 6 focused modules (all ≤500 lines): headless_authoring (459, facade + _claude_invoke + types/constants), authoring_prompts (411), page_io (392), drain_operations (353), cycle_orchestration (217), candidate_scan (123). headless_authoring stays the public import surface; cycle/scan code resolves monkeypatchable names from the root module at call time (deliberate circular import — preserves the test monkeypatch contract).
  • MAJOR-1_collect_anchor_candidates wraps _build_registry().repos in its try/except.
  • MINOR-1/2, NIT-1wall_clock_ms ≡ duration_ms documented on CycleSummary; drains_attempted excludes budget-skips; the wall-clock test is now deterministic (monkeypatches CycleBudget.exhausted instead of racing real-sleep timing).

Security

B-1 controls intact and reviewer-confirmed: --bare, --tools Read,Glob,Grep, ANTHROPIC_API_KEY fail-closed.

Verification

51 passedpytest tests_py/handlers/test_headless_authoring_throttle.py tests_py/handlers/test_consolidate.py tests_py/handlers/test_consolidate_telemetry.py tests_py/core/test_wiki_coverage.py

🤖 Generated with Claude Code

https://claude.ai/code/session_019o58McF4LRfvGNNXaqG2Au

cdeust and others added 2 commits June 30, 2026 21:59
…ti-fabrication

Make the headless wiki-authoring drain safe to enable: rewrite the
claude -p invocation as async (asyncio.create_subprocess_exec +
wait_for), bound it with a per-cycle budget (concurrency / wall-clock /
USD caps via CORTEX_HEADLESS_* env knobs), and gate authoring behind a
groundable filter so non-derivable scopes (prd/decisions/changelog/
roadmap/accessibility/localization) are never fabricated.

Reviewer fixes (REQUEST CHANGES → resolved):
- BLOCK-1: _claude_invoke now kills the subprocess in a finally block
  when returncode is None — CancelledError (a BaseException) escaped the
  except clauses and leaked a zombie; the timeout path shares the kill.
- BLOCK-3: _write_anchor_page mkdir moved inside the try/except OSError
  so a PermissionError cannot propagate through gather(return_exceptions
  =False) and abort the whole anchor phase.
- BLOCK-2: split the 1766-line module into 6 focused modules (all ≤500),
  keeping headless_authoring as the public import surface; cycle code
  resolves monkeypatchable names from the root module at call time.
- MAJOR-1: _collect_anchor_candidates wraps _build_registry().repos in
  its try/except.
- MINOR-1/2, NIT-1: document wall_clock_ms ≡ duration_ms; drains_attempted
  excludes budget-skips; deterministic wall-clock test (monkeypatch
  CycleBudget.exhausted instead of real-sleep timing).

Security controls (B-1) intact: --bare, --tools Read,Glob,Grep,
ANTHROPIC_API_KEY fail-closed. 51 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019o58McF4LRfvGNNXaqG2Au
ruff 0.15.20 format pass on the three hand-edited files, plus remove two
unused `summary` assignments (F841) and an unused `wc` import (F401) in
the throttle tests. No behavior change; 14 throttle tests still pass,
ruff format --check . and ruff check . both clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019o58McF4LRfvGNNXaqG2Au
@cdeust
cdeust merged commit e8dfb7d into main Jun 30, 2026
13 checks passed
@cdeust
cdeust deleted the fix/headless-authoring-drain-safety branch June 30, 2026 20:38
cdeust added a commit that referenced this pull request Jun 30, 2026
…ster (#72)

Lets the headless wiki-authoring drain load the user's full specialist
roster (architect, engineer, code-reviewer, …) and delegate read-only
codebase analysis to it, instead of a single confined generalist pass.

Agents mode (CORTEX_HEADLESS_AGENTS=1, default) builds the ``claude -p``
argv with ``--setting-sources user`` (loads the user roster only —
project/local sources stay excluded, so a malicious repo cannot inject
settings/hooks) + ``--tools Read,Glob,Grep,Task`` + a hard
``--disallowedTools Write,Edit,Bash,NotebookEdit`` ceiling. Solo mode
(=0) keeps the hardened ``--safe-mode`` path. Verified live against
claude CLI 2.1.197 (subscription): the roster loads; a delegated
``engineer`` subagent has NO Write tool and creates no file (the deny
ceiling propagates to subagents); no file is written to disk.

Hooks: ``--setting-sources user`` also loads the user's hooks, which
would recurse (SessionEnd → consolidation → authoring → SessionEnd …)
and pollute memory. New ``hooks/_headless_guard`` makes every Cortex
hook no-op when the worker stamps ``CORTEX_HEADLESS_AUTHORING_CHILD=1``
into the child env.

Prompts: per-page-kind delegation hint added to the section/page/anchor
builders, gated on the agents knob (omitted in solo mode where Task is
absent); the untrusted-source guard is preserved.

Root-cause fix surfaced while verifying: the variadic ``--add-dir``
swallowed the trailing positional prompt ("Input must be provided"),
so every drain with a source_root had been failing silently since #70.
The prompt now travels via STDIN — no argv-ordering fragility.

Refactor: argv/env construction extracted to ``claude_cli`` (keeps
headless_authoring < 500 lines; carries the full audit-B-1 argument).

Tests: agents/solo argv, hard-ceiling env flag, stdin-not-argv
regression, guard short-circuit (subprocess), delegation-hint splicing.
672 hooks+handlers tests green; ruff clean.


Claude-Session: https://claude.ai/code/session_019o58McF4LRfvGNNXaqG2Au

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cdeust cdeust mentioned this pull request Jun 30, 2026
cdeust added a commit that referenced this pull request Jun 30, 2026
…forgetting (#73)

Bumps version across pyproject/server/manifest/marketplace/plugin manifests and
records the 3.25.0 CHANGELOG section covering #67#72:
- feat: active forgetting module (#69), safe headless drain (#70), full zetetic
  agent roster for wiki authoring (#72)
- fix: Windows portability (#68), subscription-default billing (#71),
  variadic --add-dir prompt-swallow root cause (#72)
- deps: pydantic-settings 2.14.0→2.14.2 security patch (#67)


Claude-Session: https://claude.ai/code/session_019o58McF4LRfvGNNXaqG2Au

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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