Skip to content

fix(integrity): verify-integrity CLI crashed against running daemon (allowlist + graceful-None)#2222

Merged
vivekchand merged 1 commit into
mainfrom
fix/verify-integrity-daemon-proxy
May 28, 2026
Merged

fix(integrity): verify-integrity CLI crashed against running daemon (allowlist + graceful-None)#2222
vivekchand merged 1 commit into
mainfrom
fix/verify-integrity-daemon-proxy

Conversation

@vivekchand
Copy link
Copy Markdown
Owner

Caught by FLYWHEEL §7 live verification

Immediately after [RELEASE] of #2210 (the tamper-evident hash chain) hit PyPI as 0.12.342, I installed the fresh wheel and ran the new CLI against the running sync daemon (the live-verify step that the FLYWHEEL done-bar requires). It crashed:

$ clawmetry verify-integrity
ClawMetry Integrity Verify
────────────────────────────────────────
Traceback (most recent call last):
  File ".../clawmetry/cli.py", line 2542, in _cmd_verify_integrity
    status = result["status"]
TypeError: 'NoneType' object is not subscriptable

Root cause

_cmd_verify_integrity does store = get_store(read_only=True). When a sync daemon is running (the standard install) get_store() returns the _ProxyStore proxy because DuckDB locks at the process level — see memory reference_duckdb_process_lock and PR #1253 — and any other process opening the file (even read-only) gets rejected by the daemon's writer lock.

The proxy forwards every method call through HTTP to /__local_query__/<method> on the daemon. That endpoint enforces a per-method allowlist (_DAEMON_METHODS) so a stolen bearer token cannot call arbitrary LocalStore.<method>. verify_integrity was not in the allowlist → the daemon returned a 400 → the proxy swallowed it as None → CLI crashed.

Fix (defense in depth)

  1. routes/local_query.py — add "verify_integrity" to _DAEMON_METHODS. The proxy now succeeds against the running daemon.
  2. clawmetry/cli.py — explicit if result is None branch that prints a clear message ("could not reach the running daemon's verifier — restart the sync daemon to pick up the new wheel") and exits 2. So even if a future method is added to the CLI without being allowlisted, the CLI prints a human error instead of crashing on subscript.

Tests

tests/test_verify_integrity_cli_proxy.py — 3 new tests:

  • verify_integrity is in _DAEMON_METHODS (prevents drift).
  • CLI graceful-None branch exits 2 with the help message.
  • CLI invalid-chain branch still exits 1 with the broken-event id.

All 13 integrity-cluster tests pass locally (3 new + 10 existing in test_integrity_hash_chain.py).

Audit the class of bug, not just the instance

Per memory feedback_audit_class_of_bug_not_just_instance — checked whether any other recently added LocalStore.<method> is invoked from the CLI and missing from the allowlist. None found in the current CLI; the SIEM exporter (#2199 in flight) does not depend on the proxy.

🤖 Generated with Claude Code

The integrity hash chain shipped in 0.12.342 (#2200 / #2210), but FLYWHEEL §7
live verification surfaced an immediate crash: `clawmetry verify-integrity`
calls `get_store(read_only=True)` which returns the `_ProxyStore` proxy when
a sync daemon is running (the daemon holds DuckDB's process-level writer lock,
so any other process cannot open the file even read-only — see memory
`reference_duckdb_process_lock`). The proxy forwards each call through HTTP
to `/__local_query__/<method>`, but the daemon-side allowlist
(`_DAEMON_METHODS` in `routes/local_query.py`) did not include
`verify_integrity` — so the proxy returned `None` and the CLI crashed:

    TypeError: 'NoneType' object is not subscriptable
    at cli.py:2542  status = result["status"]

This commit is two layers of defense so the same family of bug cannot
re-emerge for any future LocalStore method exposed through the CLI:

1. Add `verify_integrity` to `_DAEMON_METHODS` (proxy now succeeds against
   the running daemon).
2. CLI defensiveness: if `result is None` (older daemon predating this fix,
   daemon unreachable, anything else returning a sentinel None), print a
   clear "could not reach the running daemon's verifier — restart the sync
   daemon" message and exit 2 instead of crashing on subscript.

Three new regression tests in `tests/test_verify_integrity_cli_proxy.py`
pin both halves: the allowlist entry, the None-graceful branch, and the
already-good "real chain break" branch.

Caught by FLYWHEEL §7 live verification — installed 0.12.342 wheel, ran
`clawmetry verify-integrity` against the running daemon, observed the
crash, fixed it. No user has hit it yet.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vivekchand vivekchand added the persona-skip Persona-pass cron skips this PR (backend-only, infra, or manually approved) label May 28, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Visual diff

Comparing ac76cd3b35e4 (head) against the PR base branch.

No flagged differences (running with ALWAYS_POST=1).

View Before After Diff
desktop overview before after diff · 0.24%
desktop flow before after diff · 0.00%
desktop brain before after diff · 0.00%
desktop usage before after diff · 0.00%
desktop crons before after diff · 0.00%
desktop memory before after diff · 0.00%
desktop security before after diff · 0.03%
desktop subagents before after diff · 0.00%
desktop transcripts before after diff · 0.00%
desktop logs before after diff · 0.00%
desktop skills before after diff · 0.00%
desktop models before after diff · 0.00%
desktop approvals before after diff · 0.00%
desktop alerts before after diff · 0.00%
desktop notifications before after diff · 0.00%
desktop context before after diff · 0.00%
desktop limits before after diff · 0.00%
desktop clusters before after diff · 0.00%
desktop history before after diff · 0.00%
mobile overview before after diff · 0.30%
mobile flow before after diff · 0.00%
mobile brain before after diff · 0.02%
mobile usage before after diff · 0.00%
mobile crons before after diff · 0.00%
mobile memory before after diff · 0.00%
mobile security before after diff · 0.00%
mobile subagents before after diff · 0.00%
mobile transcripts before after diff · 0.00%
mobile logs before after diff · 0.00%
mobile skills before after diff · 0.00%
mobile models before after diff · 0.00%
mobile approvals before after diff · 0.03%
mobile alerts before after diff · 0.00%
mobile notifications before after diff · 0.00%
mobile context before after diff · 0.00%
mobile limits before after diff · 0.00%
mobile clusters before after diff · 0.00%
mobile history before after diff · 0.00%

Folder: pr/2222/ac76cd3b35e4. Full PNGs also attached as a workflow artefact.

Generated by visual-diff bot. Pixel diffs >1% flagged; eyeball the table before merging. This check is non-blocking — fail = bot bug, not a code problem.

github-actions Bot pushed a commit that referenced this pull request May 28, 2026
@vivekchand vivekchand merged commit c2f2943 into main May 28, 2026
26 checks passed
@vivekchand vivekchand deleted the fix/verify-integrity-daemon-proxy branch May 28, 2026 07:27
vivekchand added a commit that referenced this pull request May 28, 2026
 + #2222)

CHANGELOG entries for two changes already on main:

1. #2217 — syslog/SIEM export (CEF + JSON over udp/tcp/tcp-tls). New
   Enterprise-grade exporter; off by default. Closes #2199.

2. #2222 — verify-integrity CLI daemon-proxy crash fix. Caught by
   FLYWHEEL §7 live verification immediately after 0.12.342 shipped:
   the new CLI subscript-crashed against any standard install because
   verify_integrity wasn't in the daemon-proxy method allowlist. Two-
   layer fix (allowlist + CLI graceful-None) plus 3 regression tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
vivekchand added a commit that referenced this pull request May 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

persona-skip Persona-pass cron skips this PR (backend-only, infra, or manually approved)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant