v1.6.22
feat(claudecode,hook): per-turn effort.level capture for Claude Code on both Linux CLI and Windows Desktop
Closes a gap operator-reported 2026-05-21: Claude Code's effort
dropdown (Max / Extra High / High / Medium / Low) was changing real
behavior turn-to-turn, but the dashboard's per-action Effort column
(landed in v1.6.18) stayed empty for every claude-code row. Three
distinct fragilities lined up underneath that one symptom; each is
fixed in this ship.
Why the JSONL alone can't carry effort. Exhaustive JSON-key scan
across hundreds of .jsonl files on both Windows and Linux corpora
returns zero hits for effort / reasoning_effort / budget_tokens /
thinking_budget. Effort is a request-side
thinking: {budget_tokens: N} parameter that the Anthropic API never
echoes back into the response stream. Per
code.claude.com/docs/en/hooks, effort.level IS exposed to the
PreToolUse / PostToolUse / Stop / SubagentStop hook payloads — and
that's the only per-turn source. Observer registers all four events
already, but HandleApprove was discarding the payload.
What this ship adds:
- Migration 026 — new
claudecode_effortsidecar table keyed
(session_id, tool_use_id)storingeffort_level,event_name,
received_at. The Anthropictoolu_xxxblock ID is already the
source_event_idfor tool_use rows inactions, so joins are
natural without a schema change toactions. store.UpsertClaudecodeEffort— single in-tx upsert that
populates the sidecar AND runs anUPDATEon any matching
already-inserted action row'smetadata.effort_level. Race-safe in
either ordering: hook fires before JSONL ingest (sidecar lookup
catches it on parse) or after (UPDATE stamps the row immediately).recordClaudecodeEffortincmd/observer/hook.go— extracts
(session_id, tool_use_id, effort.level)from PreToolUse + new
PostToolUse dispatch; no-ops cleanly wheneffort.levelis absent
(which the docs say happens on models that don't support effort).claudecode.Adapter.WithEffortLookup— JSONL parse-time
enrichment via per-session cached map; fail-safe on lookup errors
(parse still returns events).claude-code-windowsregistration target — mirrors
cursor-windows. Writes hooks into a Windows-side
.claude/settings.jsonwithwsl.exe -d <distro> -- <linux-bin>so
Claude Desktop on Windows can fire hooks into the WSL-side observer
binary. Auto-surfaced viaRegistry.Installed()when crossmount
detects a Windows-side.claude/.
fix(hook): three E2E fragilities that broke the chain on first ship
Discovered during empirical Desktop-side validation:
-
Git Bash MSYS path translation — Claude Code on Windows runs
hook commands through Git Bash (per the upstream docs). Git Bash's
MSYS layer auto-rewrites POSIX-shaped/home/...arguments into
C:/Program Files/Git/home/...before they reach the program being
spawned, sowsl.exe -d Ubuntu-20.04 -- /home/.../bin/observer
becamewsl.exe -d Ubuntu-20.04 -- C:/Program Files/Git/home/.../ bin/observer, which wsl.exe could not find inside the Linux distro,
exit-127 every fire. JSONL attachment records captured the symptom
verbatim:{"type":"hook_non_blocking_error", "exitCode":127, "stderr":"/bin/bash: C:/Program Files/Git/home/.../observer: No such file or directory"}Fix: prefix every registered Windows-side wsl.exe command with
MSYS_NO_PATHCONV=1so Git Bash skips POSIX→Win32 conversion. The
env-var assignment is bash-only — silently ignored by macOS/Linux
sh -cand by cmd.exe — so it's safe to set unconditionally on the
Windows registrars without OS branching. Same fix mirrored to
cursor-windows(Cursor on Windows uses the same Git Bash hook
execution path).isObserverWindowsClaudeEntry/
isObserverWindowsCursorEntryupdated to recognise both the old
prefix-free and new MSYS-prefixed shapes, so refresh-on-drift
silently upgrades existing user registrations. -
selectToolswhitelist missing-windowsvariants —
cmd/observer/init.go::selectToolshad a localsupportedmap
hard-coded to{claude-code, cursor, codex}that silently dropped
claude-code-windowsandcursor-windowsfromobserver init --alleven though both registrars existed andInstalled()
surfaced them. Auto-register-on-start was unaffected (uses
hookSupporteddirectly), but the explicitinitpath needed the
fix. Cursor-windows had been quietly broken for users running
observer initexplicitly; bonus fix included. -
db.Opendeadlined by HookTimeout —
recordClaudecodeEffortinitially fenced bothdb.Openand the
upsert undercfg.Observer.Hooks.HookTimeout()(~250-500ms write-
side budget). While the long-running daemon holds the WAL hot, a
per-process hook invocation'sdb.Openreliably busts that budget
on thequick_checkintegrity probe and silently drops the effort
write. Split the deadlines (mirrors the cursor/codex hook
handlers): unboundedcontext.Background()fordb.Open,
HookTimeoutonly on the actual write. Sidecar writes now land in
<50ms in steady state.
Migration
db.Openapplies migration 026 (newclaudecode_efforttable)
automatically on next daemon start. Zero risk: pure additive
CREATE — no existing data touched.- Existing
claude-code+cursorhook registrations on the Linux
home are untouched. Windows-sidesettings.json/hooks.json
entries previously written by Phase 2 / the cursor-windows v1.4.45
ship get refreshed in place with the newMSYS_NO_PATHCONV=1
prefix on the nextobserver startorobserver init --all --force(refresh-on-drift recognises both shapes as ours).
What this doesn't fix
- The target session
bd834194-1b9c-480f-bd14-f126a5f35a55that
prompted this investigation cannot be retroactively backfilled —
no hooks fired during its lifetime because the Windows-side
settings.jsonhadn't been written yet. Only sessions started AFTER
the registration write AND after Claude Desktop is restarted to
re-read settings.json will capture per-turn effort. - Models that don't support the
effortparameter (per Anthropic's
docs) will still leave the column blank. The hooks fire and exit 0;
recordClaudecodeEffortno-ops cleanly wheneffort.levelis empty
in the payload. Opus 4.7 and other extended-thinking-capable models
do emit it.
Verified
- Empirical end-to-end on Claude Desktop v2.1.138 (2026-05-20, fresh
install): hooks fire exit 0;claudecode_effortsidecar populates;
dashboard Effort column shows the per-turn value across Max → High
→ Low ladder changes. go test -race ./...— 51 packages OK (one inherent concurrent-
migration test flake under 51-package parallel load; passes 5/5 in
isolation).go vet ./...— clean.
Docs
- New
docs/claudecode-hook-capture.md— verified Claude Code hook
envelope, dual-registration model, MSYS gotcha, sidecar contract. docs/provider-mapping.md—claude-code-windowscell added,
MSYS_NO_PATHCONV note appended to both Windows registrars' rows.
Downloads
Pre-built binaries for each supported platform are attached below. Linux variants bundle antigravity-bridge.exe next to the observer binary for WSL2 users of the Antigravity adapter.
| Platform | Asset |
|---|---|
| Linux x86_64 | observer-v1.6.22-linux-x64.tar.gz |
| Linux arm64 | observer-v1.6.22-linux-arm64.tar.gz |
| macOS x86_64 (Intel) | observer-v1.6.22-darwin-x64.tar.gz |
| macOS arm64 (Apple Silicon) | observer-v1.6.22-darwin-arm64.tar.gz |
| Windows x86_64 | observer-v1.6.22-win32-x64.zip |
Verify with sha256sum -c SHA256SUMS (or shasum -a 256 -c SHA256SUMS on macOS) from the directory containing the downloads.
Also available via npm: npm install -g @superbased/observer@1.6.22