# Ask-Claude Payload Pattern Session-specific detail for the `ask-claude` skill: how to send questions to Claude Opus 4.8 on 10.0.0.28 reliably, capture the session_id, handle shell metacharacters, and avoid the filename-collision gotcha that bites when `read_file` returns a different schema than expected. ## Pattern: scp + ask.sh The `ask.sh` wrapper on 10.0.0.28 handles mktemp snapshot, claude invocation (`--output-format json --model claude-opus-4-8 --max-turns 30`, `timeout 280`, `|| true`), fetches the usage API, and runs `merge.py` to produce a single JSON envelope. **Why scp, not heredoc:** shell metacharacters (`"`, `$`, `(`, `)`, backticks) in the question content will break heredoc-based writes. For ANY non-trivial question write the question to a local temp file, scp it, and pass the remote path to `ask.sh --qfile`. ```bash # 1. Write question locally write_file("/tmp/ask-claude-q.txt", content=question) # 2. Copy to remote terminal("scp /tmp/ask-claude-q.txt n8n@10.0.0.28:/tmp/ask-claude-q.txt") # 3. Run via ask.sh terminal("ssh n8n@10.0.0.28 '~/claude/hermes_support/ask.sh --qfile /tmp/ask-claude-q.txt'") ``` ## JSON envelope fields Always parse these from the `--output-format json` response: | Field | When | Use | |---|---|---| | `session_id` | Always (including errors) | Capture for `--resume` follow-ups | | `result` | Success | The answer text | | `is_error` | Always | `true` if anything failed | | `subtype` | Always | `success` / `error_max_turns` / `error_budget` | | `total_cost_usd` | Always | Track spend | | `num_turns` | Always | How many agentic loops | | `usage.input_tokens` | Always | Context usage | | `usage.cache_creation_input_tokens` | Always | Context usage | | `usage.cache_read_input_tokens` | Always | Context usage | | `output_tokens` | Always | Context usage | | `modelUsage.{model}.contextWindow` | Always | Context window size — iterate keys, don't hardcode | | `stop_reason` | Always | Why Claude stopped | **Critical: capture `session_id` from EVERY response, including errors.** A turn can error yet still advance the session. If you only store it on success, `--resume` breaks. **Critical: detect `error_max_turns` via `subtype`, not `is_error`.** `error_max_turns` has `is_error: false` but no `result` field. Empty-answer detection requires checking `subtype == 'error_max_turns'`. ## Asking Claude to validate a file Claude (Opus on 10.0.0.28) has **no filesystem access to the operator's machine**. When asking Claude to validate a plan, code, or doc: 1. **Read the file locally** with `read_file` or `terminal cat > /tmp/file.txt` 2. **Inline the content in the question** as a fenced block 3. **Tell Claude explicitly** the file is pasted inline (not a path to read) **Common failure mode:** pasting a path like `/home/n8n/workspace/...` and asking Claude to validate it. Claude will reply "I can't read that file" and refuse to validate. Always inline. ## Filename-collision gotcha (execute_code + read_file) `hermes_tools.read_file` inside `execute_code` returns a different schema than the top-level `read_file` tool. Inside a script: ```python from hermes_tools import read_file r = read_file("/path/to/file") # Schema: {"status": ..., "message": ..., "path": ..., "dedup": ..., "content_returned": ...} # NO "content" key. Use the top-level read_file tool, OR use terminal cat. ``` **Workaround patterns:** ```python # Pattern 1: terminal cat to a temp file, then open() import subprocess subprocess.run(["cp", "/path/to/file", "/tmp/working.txt"]) with open("/tmp/working.txt") as f: content = f.read() # Pattern 2: shell-out to the file directly with open("/path/to/file") as f: content = f.read() # works in execute_code without hermes_tools ``` This gotcha caused patch errors in a multi-file edit session: a patch string was constructed from `r["content"]` which didn't exist inside `execute_code`. Switched to `subprocess.run` + `with open()` to read reliably. ## Pre-dispatch model and config - **Always pass `--model claude-opus-4-8` explicitly.** Profile defaults drift silently. - **Always set `--max-turns`.** Default 30 is good for analysis. Lower for trivial questions; higher (50+) for deep architectural reviews. - **Always wrap with `timeout 280`.** Prevents runaway hangs. SSH itself has 300s. - **Always add `|| true` after claude invocation.** `set -e` would kill the script on non-zero exit, but the error JSON is still parseable. - **Always use `mktemp` for temp files on the remote.** `/tmp/ask-claude-q.txt` is a fixed path that concurrent Hermes sessions clobber. ## Multi-turn session lifecycle - First `ask-claude` call in a Hermes session → fresh session (no `--resume`). - Every subsequent call in the same Hermes session → `--resume `. - Sessions auto-expire after ~5 hours of inactivity. No cleanup needed. - If the operator starts a new Hermes session (`.hermes/sessions/` rotates), the Claude session is also gone — `--resume` will fail. - For maximum continuity, capture `session_id` in the operator-visible relay text so the operator can resume manually if the Hermes session restarts. ## Usage and context alerts Apply on every turn: | Check | Threshold | Action | |---|---|---| | Context % (from JSON envelope) | ≥ 80% | Warn: "wrap up or start fresh" | | Session 5h utilization (OAuth API) | ≥ 80% | Warn + show reset time, offer Sonnet | | Weekly 7d utilization (OAuth API) | ≥ 90% | Critical warning + reset time | | Unavailable (error_budget / rate-limit) | N/A | Report exact reset time | **The OAuth usage API aggressively rate-limits.** Call at most every ~5 minutes. Cache the last good value. Swallow all errors (429s, timeouts). Never gate a turn on this call. ## Common error patterns - `error_max_turns` — question too complex, increase `--max-turns` or simplify. `is_error: false`, but no `result` field — check `subtype` explicitly. - `error_budget` — hit spending cap, check Pro limits. - Empty response — timeout (increase from 280s), OOM, or crash. - Non-JSON response — SSH dropped mid-call, Claude crashed before writing output. - `ask.sh` returns `{"is_error": true, "error": "no Claude output"}` but direct `claude -p` works — wrapper has a known failure mode. Fall back to direct: `ssh n8n@10.0.0.28 'cd ~/claude/hermes_support && timeout 280 /home/n8n/.local/bin/claude -p "$(cat /tmp/q.txt)" --output-format json --model claude-opus-4-8 --max-turns 30 || true'` ## When the operator pushes back After applying Claude's recommendations, ALWAYS emit the disagreement scan (see SKILL.md §3.5). Skipping this is a failure mode — both silently-accepting and silently-dropping are bugs. The scan is mandatory before declaring a Claude turn resolved.