fix(hooks): write hook JSON to real stdout, bypassing mcp_server redirect
mempalace.mcp_server redirects stdout → stderr at module-level import
(both Python-level and fd-level via os.dup2) to protect the MCP stdio
protocol from ChromaDB's C-level noise. Silent-save imports mcp_server
transitively via _save_diary_direct, so by the time _output() calls
print(), sys.stdout is actually stderr.
Claude Code reads hook output from fd 1. With the redirect in effect,
fd 1 points to fd 2, so our {"systemMessage": "✦ N memories woven..."}
JSON lands on stderr and Claude Code never renders it. The save still
happens, the marker still advances — the user just never sees the
beautiful checkpoint notification in their terminal.
Fix: _output() now writes to _REAL_STDOUT_FD (saved by mcp_server before
the redirect) via os.write(), falling back to sys.stdout only when the
saved fd is unavailable (e.g., hooks_cli imported without mcp_server).
Test: bash hook script 2>/dev/null now shows only the JSON;
2>&1 >/dev/null shows only the Diary entry log line — clean separation
restored.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+18
-2
@@ -134,8 +134,24 @@ def _log(message: str):
|
||||
|
||||
|
||||
def _output(data: dict):
|
||||
"""Print JSON to stdout with consistent formatting (pretty-printed)."""
|
||||
print(json.dumps(data, indent=2, ensure_ascii=False))
|
||||
"""Print JSON to the real stdout, even if mcp_server has hijacked sys.stdout.
|
||||
|
||||
mempalace.mcp_server redirects stdout → stderr at module import (fd and
|
||||
sys-level) to protect the MCP stdio protocol from ChromaDB's C-level
|
||||
prints. Silent-save imports it transitively via _save_diary_direct, so
|
||||
sys.stdout is stderr by the time we get here. Claude Code reads hook
|
||||
output from fd 1, so we write there directly using the saved fd.
|
||||
"""
|
||||
payload = json.dumps(data, indent=2, ensure_ascii=False) + "\n"
|
||||
try:
|
||||
from .mcp_server import _REAL_STDOUT_FD
|
||||
if _REAL_STDOUT_FD is not None:
|
||||
os.write(_REAL_STDOUT_FD, payload.encode("utf-8"))
|
||||
return
|
||||
except Exception:
|
||||
pass
|
||||
sys.stdout.write(payload)
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def _get_mine_dir(transcript_path: str = "") -> str:
|
||||
|
||||
Reference in New Issue
Block a user