fix(mcp): bound version-detection git subprocess - #3038
Conversation
headroom_compress hangs (60s MCP timeout) because the compression pipeline initializes the OTel tracer, which calls get_version() -> list_release_tags() -> 'git tag -l v*' via subprocess.run with no timeout and inherited stdin. In a stdio MCP server, git inherits the stdio pipe and hangs. Add stdin=DEVNULL + timeout=10 to the three version-detection git calls (list_release_tags, list_release_commits, commit_height_since).
PR governanceThis PR follows the template and is marked ready for human review. |
JerrettDavis
left a comment
There was a problem hiding this comment.
Reviewed the refreshed exact head adversarially. The source-tree version path can execute during telemetry initialization, and all three Git subprocesses are now isolated from MCP stdio and bounded. A timeout from the first lookup is contained by _source_tree_version, allowing get_version() to continue through its existing packaged/distribution fallback chain without affecting compression. I verified that behavior explicitly (the local editable environment returned its installed 0.35.0 metadata rather than hanging), and the focused release/observability suite passes 29/29 with Ruff and diff checks clean. I refreshed the branch to current upstream main and corrected the fallback wording in the description; no merge action was taken. No blocking correctness findings.
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Description
headroom_compresshangs (60s-32001MCP timeout) in stdio MCP servers. Root cause: the compression pipeline init → OpenTelemetry tracer →get_version()→_source_tree_version()→list_release_tags()runsgit tag -l v*viasubprocess.runwith no timeout and inherited stdin. In a stdio MCP server,gitinherits the stdio pipe and hangs, soawait run_in_executor(...)blocks until the client's 60s timeout.Fix: pass
stdin=DEVNULL(stop inheriting the stdio pipe) +timeout=10(fail gracefully through the existingtry/exceptand continue through the packaged, distribution-metadata, or"unknown"fallback chain) on the three version-detection git calls.Type of Change
Changes Made
headroom/release_version.py: addstdin=subprocess.DEVNULL+timeout=10tolist_release_tags,list_release_commits, andcommit_height_sincegit subprocess calls.tests/test_release_version.py: addtest_version_detection_git_calls_are_hang_safeasserting the three git calls are bounded and do not inherit the caller's stdin.Testing
pytest)ruff check .)mypy headroom)Test Output
Real Behavior Proof
0.36.0-dev(editable install),headroom mcp serve(stdio transport), no proxy.headroom mcp serveand sent a raw MCPtools/callforheadroom_compresswith{"content":"{"name":"test","items":[1,2,3,4,5]}"}. Also verified through the dsh web UI at http://127.0.0.1:3080 (same tool, routed through a running proxy).tools/callhung 70s and returnedError: MCP error -32001: Request timed out. After the fix,tools/callreturned in 2.38s:{"hash":"a76e1fa6b2611dc18b031c6d","original_tokens":24,"compressed_tokens":24,"tokens_saved":0,"transforms":["router:noop"]}The dsh web UI returned the same hash
a76e1fa6b2611dc18b031c6d.headroom_retrieve/headroom_statsagainst a live proxy; Linux/macOS stdio MCP clients; the git-hang path with a running proxy (the proxy is not in the tool-call path — the hang reproduces with no proxy).Runtime Rollout Safety
"unknown"fallback chain instead of hanging when a git subprocess exceeds 10s. Compression behavior is unchanged.try/except.Review Readiness
Checklist
CHANGELOG.md— it is generated by release-please from my Conventional Commit PR title (a CI guard enforces this)Screenshots (if applicable)
N/A
Additional Notes
tests/test_release_workflows.py::test_no_native_tls_in_wheel_build_treefails locally withFileNotFoundError(missing Windows build tool) — pre-existing and unrelated to this change.stdin=DEVNULL+timeout=10are self-explanatory), the rationale is captured in the commit message and test docstring, and no API/behavior surface changed to document.headroom mcp servefor any stdio client (claude/codex/etc.).