Resolve UUID connectors from only the newest session file#201
Conversation
The connector resolver globbed **/local_*.json across both session folders, sorted (stat-ing) every match, and read all of them. CoWork's local-agent-mode-sessions grows a deep outputs/ subtree per session (~1200 entries locally), so on every mcp__ call the recursive walk + stat-all + read-all added latency that scales with usage and can breach the hook timeout — silently skipping UUID resolution (and policy) for that call. remoteMcpServersConfig is the desktop-wide connector registry snapshotted into every session file, so the single newest local_*.json already holds the current list. Glob only the known depth (<dir>/*/*/local_*.json) so we never descend into outputs/, track the newest file, and read just that one. ~1.9ms vs ~23ms+.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 76188fe. Configure here.
| try: | ||
| data = json.loads(latest.read_text(encoding='utf-8')) | ||
| except Exception: | ||
| return None |
There was a problem hiding this comment.
Newest session read failure
Medium Severity
After picking the newest local_*.json, any I/O or JSON parse error on that file makes _resolve_claude_code_session_connector return None immediately. The prior implementation skipped bad files and kept searching older session snapshots, so a transient corrupt or half-written newest file can leave MCP calls on a raw UUID with no policy even when older files still resolve the connector.
Reviewed by Cursor Bugbot for commit 76188fe. Configure here.
vigneshsubbiah16
left a comment
There was a problem hiding this comment.
🛡️ Automated Security Review (consensus)
2 findings — 1 high-confidence, 1 to triage. Reviewers: Cursor, Claude, Semgrep, Gitleaks.
🔴 Single-snapshot resolver fails open on corrupt or attacker-controlled newest file
claude-code/hooks/unbound.py:939-942
Impact: The resolver now reads only the newest local_*.json and returns None on JSON parse failure or UUID miss, so a corrupt or attacker-planted snapshot disables connector resolution for all subsequent mcp__ calls (raw UUID → no policy metadata); the sessions tree is user-writable and timestamps are trivially spoofable, and parse failures are silent.
Fix: On parse failure or miss in the newest file, fall back to the next-newest bounded-glob candidates (cap reads at 2–3 files) and log a warning when the newest snapshot is unusable.
Flagged by: Claude, Cursor (lead)
🟡 Overly permissive file permissions (pre-existing, outside diff)
claude-code/hooks/unbound.py:1761, claude-code/hooks/unbound.py:2061
Impact: 0o755 / $BITS file modes may grant broader local filesystem access than necessary for hook artifacts.
Fix: Use 0o644 (or tighter) unless the execute bit is required.
Flagged by: Semgrep
Previously acknowledged (not re-flagged)
- Brief resolution miss for brand-new connectors not yet in the newest snapshot — Accepted in PR description: that call stays a UUID until the registry appears in the latest snapshot.
🤖 consensus review · reviewers: Cursor,Claude,Semgrep,Gitleaks · head 76188fe1 · 2026-07-03T09:51Z
| if latest is None: | ||
| return None |
There was a problem hiding this comment.
No log entry when no session files are found
When latest is None (either because the session directories are empty or because no */*/local_*.json files matched), the function returns None silently. With the fixed-depth glob replacing the old **/ recursive glob, a depth mismatch — e.g., a future Claude version that nests files one level deeper — would produce exactly this silent None on every call, giving no indication in logs that the glob pattern is the problem.


What
Fixes a performance regression in the Claude-desktop connector resolver (
_resolve_claude_code_session_connector) that could silently drop UUID→name resolution on device.Problem
Since we added
local-agent-mode-sessions(CoWork) to the resolver, it globbed**/local_*.jsonacross both folders,stat()-ed every match to sort, and read all of them — on everymcp__tool call. CoWork grows a deepoutputs/subtree per session (~1,200 fs entries locally for 30 session files), so the recursive walk + stat-all + read-all adds latency that scales with usage and can breach the hook timeout → the call proceeds with the raw UUID (no resolution, no policy). This is the likely reason UUID entries kept appearing after the two-folder change.Fix
remoteMcpServersConfigis the desktop-wide connector registry, snapshotted into every session file, so the single newestlocal_*.jsonalready holds the current list. So:<sessions-dir>/*/*/local_*.json— never descends intooutputs/.Verified on-device: bounded glob finds the exact same files as recursive (2 and 30), resolves all connectors correctly (Google Calendar / Linear / Notion), ~1.9ms vs ~23ms+.
Note
If a connector is invoked before it lands in the newest snapshot, resolve returns
None(that call stays a UUID) — acceptable since the newest file reflects the current registry.py_compileclean.Note
Low Risk
Localized performance fix in hook MCP metadata resolution; behavior change is limited to using the newest session snapshot only, with a brief window where resolution may miss brand-new connectors.
Overview
Speeds up UUID→desktop-connector resolution on every
mcp__PreToolUse path by changing how_resolve_claude_code_session_connectorfinds Claude session snapshots.Instead of recursively globbing
**/local_*.json(which walked CoWorkoutputs/trees), stat-ing every match, sorting, and JSON-parsing each file until a UUID hit, the resolver now globs only*/*/local_*.jsonunder each session root, picks the single newest file by creation/mtime, and reads only that file’sremoteMcpServersConfig. That matches the assumption that the desktop connector registry is duplicated in every session file, so the latest snapshot is enough.Trade-off: if a connector isn’t in the newest snapshot yet, resolution returns
Nonefor that call (raw UUID, no policy metadata) until the registry appears there.Reviewed by Cursor Bugbot for commit 76188fe. Bugbot is set up for automated code reviews on this repo. Configure here.
Greptile Summary
Optimises
_resolve_claude_code_session_connectorby switching from a recursive**/local_*.jsonglob that stat-called every file in both session directories to a fixed-depth*/*/local_*.jsonglob that reads only the single newest file, bringing per-call latency from ~23 ms to ~1.9 ms and preventing hook timeouts that caused UUID pass-through.**/local_*.json→*/*/local_*.jsonbounds the walk to exactly 2 levels under each session directory, avoiding the deepoutputs/subtree added bylocal-agent-mode-sessions.remoteMcpServersConfigis a desktop-wide snapshot present in every session file.Nonewith no fallback; two new early-return paths are currently silent (no log on parse failure, no log when no files are found).Confidence Score: 4/5
Safe to merge; the performance fix is correct and well-reasoned, with the only concerns being two silent return None paths that reduce debuggability.
The core logic change is sound and the on-device verification is convincing. The two unlogged early exits — a failed JSON parse on the sole read target and the case where the bounded glob finds nothing — make it harder to distinguish a corrupt session file from a legitimately absent connector in production logs.
claude-code/hooks/unbound.py — specifically the two new return None paths (parse failure and no-files-found) that are currently silent.
Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A["_resolve_claude_code_session_connector(server_uuid)"] --> B{Is server_uuid a UUID?} B -- No --> Z1["return None"] B -- Yes --> C["Iterate _claude_session_dirs()\n(claude-code-sessions, local-agent-mode-sessions)"] C --> D["base.glob('*/*/local_*.json')\n(fixed depth — skips outputs/ subtree)"] D --> E["For each candidate:\n_session_file_created_at(f)"] E --> F{ts > latest_ts?} F -- Yes --> G["Update latest, latest_ts"] F -- No --> E G --> E E --> H{latest is None?} H -- Yes --> Z2["return None\n⚠ silent — no log"] H -- No --> I["latest.read_text() → json.loads()"] I -- parse error --> Z3["return None\n⚠ silent — no log"] I -- success --> J["Iterate remoteMcpServersConfig entries"] J --> K{entry.uuid matches?} K -- No --> J K -- Yes --> L["Build cfg dict\n(name, url, type, scope)"] L --> M["return (name, cfg)"] J -- exhausted --> Z4["return None"]%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%% flowchart TD A["_resolve_claude_code_session_connector(server_uuid)"] --> B{Is server_uuid a UUID?} B -- No --> Z1["return None"] B -- Yes --> C["Iterate _claude_session_dirs()\n(claude-code-sessions, local-agent-mode-sessions)"] C --> D["base.glob('*/*/local_*.json')\n(fixed depth — skips outputs/ subtree)"] D --> E["For each candidate:\n_session_file_created_at(f)"] E --> F{ts > latest_ts?} F -- Yes --> G["Update latest, latest_ts"] F -- No --> E G --> E E --> H{latest is None?} H -- Yes --> Z2["return None\n⚠ silent — no log"] H -- No --> I["latest.read_text() → json.loads()"] I -- parse error --> Z3["return None\n⚠ silent — no log"] I -- success --> J["Iterate remoteMcpServersConfig entries"] J --> K{entry.uuid matches?} K -- No --> J K -- Yes --> L["Build cfg dict\n(name, url, type, scope)"] L --> M["return (name, cfg)"] J -- exhausted --> Z4["return None"]Reviews (1): Last reviewed commit: "Resolve connectors from only the newest ..." | Re-trigger Greptile