Skip to content
Back to skills

Interface Probe

ASecurity

Runs a controlled probe against the real pinned Claude Code binary to answer a wire-format question (hook payloads, status-line JSON, CLI behaviour), using the isolated rig in test/rig/. Findings land as fact records in docs/facts/.

  • 2 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 19, 2026
developmentrustgobashgitapi

Works with

  • claude code
  • cli
  • api

Security analysis

A96/100
  • mediumUses curl or wget to download content

Pro shows the line behind each finding and how to fix it

Scanned October 1, 2026

npx -y skills add Zalaras/muster --skill interface-probe --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Interface Probe?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Interface Probe
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/zalaras-interface-probe/badge)](https://www.skillsdirectory.com/skills/zalaras-interface-probe)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: interface-probe
description: "Runs a controlled probe against the real pinned Claude Code binary to answer a wire-format question (hook payloads, status-line JSON, CLI behaviour), using the isolated rig in test/rig/. Findings land as fact records in docs/facts/."
argument-hint: "<question to settle>"
allowed-tools: Bash, Read, Write, Edit, Grep, Glob
---

> Maintainer note: a skill in the main session because it drives tmux interactively and talks
> through ambiguous captures, which a subagent can't; the rig is `test/rig/` (history: `spikes/RIG.md`).

`make canary` (`test/canary/harness_test.go`) drives the same production chain automatically but
cannot answer *new* questions — that is this skill's job. Known gotchas are fact records;
`go run ./tools/kb show kb:fact/<slug>` tells you which versions each holds on.

You are running an interface probe: a controlled experiment against the **real**
Claude Code binary to settle a question the docs can't be trusted to answer
(they have been wrong before). The question to
settle: **$ARGUMENTS**

## Rules

1. **`~/.claude/settings.json` and `settings.local.json` are never read or modified.**
   The developer has live sessions on them. Isolation comes from the project-scoped
   `.claude/settings.json` the rig writes into each instance's scratch repo —
   nothing else. The one sanctioned touch is a hash, which reads no content: record
   `md5 -q ~/.claude/settings.json` before the run and compare it after (§ 6; compare the two
   hashes, don't assume a value).
2. **Never set `CLAUDE_CONFIG_DIR` for a session that must reach the API.** It
   yields a config dir with no credentials and dies with `Not logged in`; leave it unset
   rather than attempting to log in. (Its one legitimate use: zero-token `StopFailure` induction,
   below.)
3. **Real sessions burn the developer's subscription.** Always `--model
   claude-haiku-4-5-20251001`, trivial prompts ("say hi"), and tear down when done —
   an orphan keeps burning. Prefer the zero-token inductions wherever they answer
   the question.
4. **tmux only via the instance's private socket** (`tmux -S "$PROBE_SOCKET"` — env.sh exports a socket *path*, so `-S`, never `-L`),
   never the developer's default server.
5. Ports are `878<index>` (8780–8789) — 5000 and 7000 belong to AirPlay.

## 1. Stamp an instance

Pick a single-digit index whose port is free (`lsof -nP -iTCP:878<i> -sTCP:LISTEN`).

```bash
cd <repo-root>
./test/rig/newprobe.sh 3               # <- your index
. /tmp/muster-probe/instances/3/env.sh # exports PROBE_PORT, PROBE_REPO, PROBE_SOCKET, PROBE_CAPTURE ...
echo "${CLAUDE_CONFIG_DIR:-<unset>}"   # must print <unset>
```

Instances deliberately live in `/tmp/muster-probe` (override: `MUSTER_PROBE_HOME`),
**outside the repo tree**: Claude Code loads CLAUDE.md from every parent directory,
so a scratch repo inside muster/ or ~/Documents contaminates the probe session with
Muster's and the developer's instructions. Captures still land in `test/rig/captures/`.

Idempotent — re-running rewrites settings/scripts, leaves the repo and captures
alone. The generated settings register **every** hook event at the capture server
(http, 2 s timeouts), plus a `type:"command"` wrapper for `SessionStart` (which
silently never fires over http), plus the status line. Events added after 2.1.233 post twice,
command at `/hook/<event>` and http at `/hook/<event>-http` — count one path per event.
`WorktreeCreate`/`WorktreeRemove` are registered only with `MUSTER_PROBE_WORKTREE_HOOK=1`, since
the server's empty 200 fails `--worktree` and `EnterWorktree` (kb:fact/worktree-create-hook-owns-path).

## 2. Start the capture server

```bash
go build -o bin/probe-capture ./test/rig/capture
nohup bin/probe-capture -port $PROBE_PORT -instance $PROBE_IDX \
  -dir test/rig/captures > test/rig/captures/capture-$PROBE_IDX.log 2>&1 &
curl -s http://127.0.0.1:$PROBE_PORT/health   # -> ok
```

- `POST /hook/{event}` → appends to JSONL, replies 200 empty (or a canned body via
  `-decision '{"decision":"allow"}'` / `MUSTER_PROBE_DECISION` — for
  PermissionRequest work).
- `POST /statusline` → appends, replies `MUSTER-PROBE` — seeing that string render
  in the TUI is your proof the status line works. `MUSTER-PROBE(post-failed)`
  rendered means your capture server is down.
- Credential-shaped headers are stored `<redacted>`; payloads are never printed to
  stdout (they contain prompt text — never copy them anywhere world-readable).

## 3. Drive a session

**Headless** (preferred when the question doesn't need the TUI — hooks fire in
`-p` mode too):

```bash
cd $PROBE_REPO && /Users/damian/.local/bin/claude -p "say hi" \
  --model claude-haiku-4-5-20251001 </dev/null
```

**Interactive** (TUI/status-line/mode questions):

```bash
export PATH=/usr/local/bin:$PATH        # tmux 3.7b
tmux -S "$PROBE_SOCKET" new-session -d -s s1 -x 200 -y 50 -c "$PROBE_REPO"
tmux -S "$PROBE_SOCKET" set-option -g escape-time 0
tmux -S "$PROBE_SOCKET" set-option -g status off
tmux -S "$PROBE_SOCKET" set-option -g focus-events on
tmux -S "$PROBE_SOCKET" send-keys -t s1 \
  'export LANG=en_US.UTF-8 TERM=xterm-256color; cd '"$PROBE_REPO"'; unset CLAUDE_CONFIG_DIR; /Users/damian/.local/bin/claude --model claude-haiku-4-5-20251001' Enter
```

- `-x 200 -y 50` is load-bearing (default 80x24 wraps the TUI into garbage);
  `LANG`/`TERM` prevent mojibake.
- **First launch in a fresh repo blocks on the workspace-trust prompt** — no hooks,
  no status line until answered. Poll `tmux -S "$PROBE_SOCKET" capture-pane -p -t s1`
  for "Quick safety check", then answer it. The preselected option varies by version (2.1.233:
  "trust", so a bare `Enter`; 2.1.259: "No, exit", so `Down` then `Enter`) — read the pane's `❯`
  marker rather than assuming either.
  Headless runs do **not** record trust; interactive hits it anyway.
- Startup takes 10–20 s; a blank pane is normal, poll — don't conclude failure.
- Type and submit **separately**: `send-keys -t s1 'say hi'`, wait ~1 s, then
  `send-keys -t s1 Enter`. One combined call gets the newline swallowed.
- Polling is fine here: this skill runs in the main session, which the harness wakes (the
  no-poll rule binds subagents). Foreground `sleep` is blocked, so put waits inside a
  backgrounded bash command. `timeout`/`gtimeout` are not installed.

## 4. Induction recipes (deterministic, zero real tokens)

**`StopFailure` / `authentication_failed`** — fails before any API call, ~1 s,
also emits `UserPromptSubmit` + `SessionEnd`, so it exercises a whole failure
sequence for free:

```bash
cd $PROBE_REPO
CLAUDE_CONFIG_DIR=/tmp/muster-probe/instances/$PROBE_IDX/claude-config \
  /Users/damian/.local/bin/claude -p "hi" --model claude-haiku-4-5-20251001 </dev/null
```

(Only this flavour fails before the API; other `error` values need the proxy.)

**`StopFailure` / API-error flavours** — `ANTHROPIC_BASE_URL` *is* honoured under
subscription OAuth. Plain mode fails every request (zero tokens); `-upstream`
mode lets N `/v1/messages` through first to induce a genuinely **mid-turn** death:

```bash
go build -o bin/probe-failproxy ./test/rig/failproxy
bin/probe-failproxy -port 879$PROBE_IDX -status 500 &            # every call fails
# -message sets error.message (some mappings key on it); -header Name:value (repeatable) adds
# response headers, e.g. anthropic-ratelimit-unified-status:rejected on a 429
# or: bin/probe-failproxy -port 879$PROBE_IDX -upstream https://api.anthropic.com -fail-after 1 &
cd $PROBE_REPO && ANTHROPIC_BASE_URL=http://127.0.0.1:879$PROBE_IDX \
  /Users/damian/.local/bin/claude -p "say hi" --model claude-haiku-4-5-20251001 </dev/null
```

`CLAUDE_CODE_MAX_RETRIES=0` skips the ~90 s retry backoff on 429/5xx, so each induction takes
~5 s. Avoid `-upstream` for anything that sends a prompt: it multiplies cache tokens
many times over.

## 5. Read the captures

```bash
jq -c '{event,at:.received_at}' "$PROBE_CAPTURE" | grep -v '"event":null'
jq 'select(.path=="/statusline") | .body' "$PROBE_CAPTURE" | tail -40
```

Each line: `{received_at, path, method, event, headers, body_raw, body, body_is_json}`.

Analysis traps (each has produced a wrong conclusion):

- **Group by `session_id` before aggregating.** Any field that latches on
  mid-session looks intermittent when pooled across short-lived sessions.
- **`rate_limits` appears only after the first completed turn**; startup posts have
  no `rate_limits` and null `context_window.used_percentage`. Send a prompt before
  asserting on either.
- Cheap fast sessions are good for payload *shapes*, bad for anything *temporal* —
  keep a long-lived session in the sample when the question is "when".
- Flagging a gap is not a licence to assert the conclusion the gap forbids.

## 6. Tear down (always)

```bash
tmux -S "$PROBE_SOCKET" kill-server 2>/dev/null
pkill -f "probe-capture -port $PROBE_PORT"; pkill -f probe-failproxy
ps aux | grep '[c]laude'                 # none of YOURS survived (the developer's own sessions will be here — leave them)
md5 -q ~/.claude/settings.json           # matches your baseline
```

## 7. Where findings land

Per CLAUDE.md doc upkeep, before the session ends:

- New wire-format fact → a record in `docs/facts/` with `verified:` the version the payloads
  report, `refs:` the capture path with its count ("n of m sessions"), and `guard:` the canary or
  unit test that pins it (`none` is allowed; offer the backlog line and let the developer file it —
  kb:adr/process-backlog-entries-are-the-users-to-file). A fact proved wrong
  gets its ceiling pinned and a new record linked by `refs`, never a rewrite.
- A settled open question → an `accepted` ADR (probes run on `main` with the developer present).
- Tick anything this closes in `TODO.md`.
- `make gen-kb && make check-kb`, generated files in the same commit.
- State the Claude Code version the evidence was captured against (from
  `SessionStart`/status-line payloads), and whether it matches the pin.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.