feat(timeout): #224 ledger-query timeout with Claude-hooks context surfacing#323
Merged
Merged
Conversation
…rfacing
Two-layer gate architecture for SurrealDB ledger queries:
1. Deterministic server-side gate (the floor):
- asyncio.wait_for wrap on LedgerClient.query/execute/execute_many
- New LedgerTimeoutError(LedgerError) with sql_prefix/timeout_class/
elapsed_seconds/budget_seconds (sql truncated to 200 chars)
- Two budget classes: read (5s default) and drift (30s default)
- Fail-closed config readers in context.py: clamp on out-of-range,
fall back to default on NaN/Inf/string/bool/zero/negative
- BICAMERAL_QUERY_TIMEOUT_DISABLE env-override for debugging
(mirrors BICAMERAL_INGEST_RATE_LIMIT_DISABLE precedent)
- Registered in governance-gates.yaml per #205 doctrine
2. Claude-Code hook layer (advisory, additive, never blocking):
- .claude/hooks/session_start_timeout_posture.py: one-line stderr
brief at session start (budgets, last-hour timeout counts,
env-disable state)
- .claude/hooks/pre_tool_use_timeout_context.py: stderr warning
before bicameral tool calls when recent timeouts (<10min) exist
- New recent_timeout_count field on PreflightResponse (additive,
default {"read":0,"drift":0}); backed by in-memory ring buffer
(cap 1000) in ledger/timeout_telemetry.py
- Both hooks exit 0 unconditionally; graceful degrade if MCP
unreachable
Handler annotations: handlers/history.py:_fetch_all_decisions_enriched
takes timeout_class="drift" (the only single-query graph-traversal
site). All other handlers stay on the 5s read default — composite
workflows like preflight/sync_middleware/link_commit chain many
individually-fast queries; each carries its own read budget.
Tests (47 new, all passing):
- tests/test_query_timeout_unit.py (25): wrap behavior, fail-closed
config across negative/string/bool/NaN/Inf/clamp paths, env-disable
bypass, sql-prefix truncation, telemetry ring-buffer recording,
execute() parity, BicameralContext default construction.
- tests/test_query_timeout_handler_routing.py (13): static-grep pins
on drift-class annotation; sanity-check that exactly one handler
site uses drift; governance-gates.yaml entry present; LedgerClient
signature exposes Literal["read","drift"] kwarg.
- tests/test_claude_hooks_timeout_context.py (9): subprocess
invocation of both hook scripts, env-disable surfacing, missing-
import graceful exit, ring-buffer cap at 1000, window filtering.
Docs:
- docs/policies/query-timeouts.md
- docs/policies/claude-hooks-mcp-integration.md
Plan: plan-224-surrealdb-query-timeout.md (audited PASS at L2)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
9 tasks
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.
Summary
Two-layer timeout architecture for SurrealDB ledger queries — closes BicameralAI/bicameral-daemon#22 + addresses operator directive 2026-05-13 ("with Claude specifically we need to leverage hooks that make calls to the MCP for relative context at an appropriate gate").
Layer 1 — Deterministic server-side gate (the floor)
asyncio.wait_forwrap on everyLedgerClient.query/execute/execute_manyLedgerTimeoutError(LedgerError)carryingsql_prefix(200-char cap) /timeout_class/elapsed_seconds/budget_secondsread(5s default) anddrift(30s default), clamped to safe ranges incontext.pyBICAMERAL_QUERY_TIMEOUT_DISABLE=1env-override for debugging (mirrors theBICAMERAL_INGEST_RATE_LIMIT_DISABLEprecedent)governance-gates.yamlper BicameralAI/bicameral-daemon#34 doctrine — skill text is advisory, the wrap is the truthLayer 2 — Claude Code hooks (advisory context, additive)
.claude/hooks/session_start_timeout_posture.py— one-line stderr brief at session start (budgets, last-hour timeout counts, env-disable state).claude/hooks/pre_tool_use_timeout_context.py— stderr warning before bicameral tool calls when recent (<10 min) timeouts existrecent_timeout_countfield onPreflightResponse(additive, default{""read"":0, ""drift"":0}); backed by in-memory ring buffer (cap 1000) inledger/timeout_telemetry.pyHandler annotations
Narrowed during Phase B based on evidence: only
handlers/history.py::_fetch_all_decisions_enrichedis a single graph-traversal query needing the 30s budget. Other workflows (preflight, sync_middleware, link_commit) chain many individually-fast queries; each carries its own 5sreadbudget. The plan-doc records the evidence-based narrowing.Test plan
tests/test_query_timeout_unit.py— 25 tests covering wrap behavior, fail-closed config (negative/string/bool/NaN/Inf/clamp/zero), env-disable bypass, sql-prefix truncation, telemetry ring-buffer recording,execute()parity,BicameralContextdefault constructiontests/test_query_timeout_handler_routing.py— 13 static-grep pins (exactly one handler usesdriftannotation;governance-gates.yamlentry present;LedgerClientsignature exposesLiteral[""read"",""drift""]kwarg)tests/test_claude_hooks_timeout_context.py— 9 subprocess invocations of both hook scripts (exit 0; env-disable surfacing; missing-import graceful exit; ring-buffer cap; window filtering)tests/test_history_input_span_id.py,tests/test_preflight_attribution_redaction.py) — 15 tests still pass with the new annotation +recent_timeout_countfieldtests/test_context_ingest_max_bytes.py,tests/test_context_ingest_rate_limit.py) — 13 tests pass; the new_read_query_timeout_*_secondsreaders mirror the existing patternruff format+ruff checkclean on all 13 modified/new source filesscripts/lint_skill_governance.pyparses the new gate entry without errorDocs
docs/policies/query-timeouts.md— operator-facing reference (defaults, config, env override, error shape, telemetry, governance)docs/policies/claude-hooks-mcp-integration.md— design rationale, wiring instructions for.claude/settings.json, constraints, pattern for adding future hooks at other gatesPlan + audit
plan-224-surrealdb-query-timeout.md(in this PR's diff)qor-judgePASS at L2 — two non-blocking observations resolved during implementationReview Boundary
This PR is the explicit-authorization push of locally-committed work. Per the persistent Review Boundary, neither this PR nor the underlying commit was created without operator authorization.
🤖 Generated with Claude Code