magpie-setup-isolated-setup-doctor · git:20260913.a4dd8ba · 2026-09-13 · sha256 d0d7c6398c4a9186
magpie-setup-isolated-setup-doctor git:20260913.a4dd8baB
Immutable. This exact content is served forever at /api/v1/blob/d0d7c6398c4a9186.
---
# SPDX-License-Identifier: Apache-2.0
# https://www.apache.org/licenses/LICENSE-2.0
name: magpie-setup-isolated-setup-doctor
family: setup
mode: Meta
description: |
Probe the secure-agent setup for in-session functional
restrictions that block legitimate workflows in Claude Code,
Codex, or Gemini CLI. Runtime-specific diagnostics; Claude has three live
probes — SSH agent / Yubikey reachability, localhost port
binding, docker / podman runtime socket — each pointing the
user at the matching numbered troubleshooting entry and its
settings.json remediation (see body). Read-only — never
modifies settings.json, never invokes the sandbox bypass.
when_to_use: |
Invoke when the user says "doctor my sandbox", "diagnose
sandbox friction", "why is the sandbox blocking X", "check
whether ssh / docker / port-bind works inside the sandbox",
or after the user reports a workflow failure that smells
sandbox-shaped (agent unreachable, socket errors, port
permission errors). Also a good periodic check after every
agent harness upgrade — the sandbox profile evolves and a
previously-working call may have moved into deny.
capability:
- capability:platform
- capability:reassess
license: Apache-2.0
---
<!-- Placeholder convention (see AGENTS.md#placeholder-convention-used-in-skill-files):
<project-config> → adopting project's `.apache-magpie/` directory -->
# setup-isolated-setup-doctor
## Runtime routing (run before the Claude-specific probes)
Use the operator's explicitly requested runtime when supplied; otherwise use the active session's runtime.
An installed executable or configuration directory alone does not select a runtime.
For the routing below, treat that selection as the active harness.
When the active harness is Codex, first require the static verification in
[docs/adapters/codex.md](../../../../docs/adapters/codex.md#verify), then run the
shared live environment probes inside the active Codex sandbox. Attribute
failures separately to native sandbox/network denial, approval policy, or
the POSIX agent-iso layer. Do not prescribe a `.claude` settings change for
a Codex failure. Then stop before the Claude-specific branch below.
When the selected runtime is Gemini CLI, follow
[docs/adapters/gemini.md](../../../../docs/adapters/gemini.md#doctor): verify the profile first, then diagnose the actual tool result in the active Gemini session.
Distinguish policy refusal, sandbox expansion, hook or trust failures, and wrapper or authentication problems.
Do not require Claude configuration or prescribe Claude settings changes; then stop before the Claude-specific probes below.
When the harness is Claude Code, continue below. If the harness cannot be
determined, ask once.
The **diagnostic** layer over the secure agent setup. Complements
the existing setup skills:
- [`setup-isolated-setup-install`](../isolated-setup-install/SKILL.md)
installs the secure setup.
- [`setup-isolated-setup-verify`](../isolated-setup-verify/SKILL.md)
answers *"is the secure setup **installed** correctly?"* —
static checks on settings.json shape, hook wiring, pinned tool
versions. Catches drift and missing pieces.
- [`setup-isolated-setup-update`](../isolated-setup-update/SKILL.md)
surfaces drift against the framework's latest.
- **`setup-isolated-setup-doctor` (this skill)** answers *"are
common workflows **functionally** blocked by the current
sandbox?"* — live probes of SSH agent, port binding, docker /
podman socket. Catches over-restrictive allowlists.
Run `verify` first when the install is in question (fresh
machine, recent framework upgrade, sandbox-state surprise). Run
`doctor` when the install is known good but a workflow fails in
a sandbox-shaped way — agent unreachable, socket error, port
permission error.
Every probe maps to a numbered entry in
[`docs/setup/sandbox-troubleshooting.md`](../../../../docs/setup/sandbox-troubleshooting.md);
the doctor's job is to identify *which* entry applies right now,
not to re-explain the remediation. If a fail surfaces a failure
mode not catalogued there, propose appending a new entry per the
catalog's *Adding a new entry* section.
## Golden rules
- **Read-only.** Each probe runs a small, deterministic,
side-effect-free check. The skill never edits any settings
file, never runs a command with `dangerouslyDisableSandbox`,
never installs anything. If a check fails, surface the failure
and point at the catalog entry; do not auto-fix.
- **Run every probe, even on early failure.** Do not stop at the
first ✗. The value of the report is in the full picture — a
user may have one of three independent restrictions, or all
three, and discovering them one re-run at a time is annoying.
- **Distinguish ✗ (failing) from ⊘ (not applicable).** ✗ means
the probe ran and the sandbox blocked it. ⊘ means the probe
was skipped because the prerequisite is absent (e.g. no
`docker` / `podman` on `PATH` → docker probe ⊘, not ✗).
- **Surface evidence.** Each report line names the probe command,
the exit code, and the relevant stderr snippet. "Looks
blocked" is not a useful report; "ssh-add -l → rc=2 →
`Could not open a connection to your authentication agent`" is.
- **Map each ✗ to a catalog entry.** The fail report includes a
direct link to the matching section of
[`docs/setup/sandbox-troubleshooting.md`](../../../../docs/setup/sandbox-troubleshooting.md).
Do not paraphrase the remediation — the catalog is the single
source of truth.
## The 3 probes
The current set covers the three failure modes the catalog
documents. New probes are added when new entries land in the
catalog; the two stay in lock-step.
### Probe 1 — SSH agent / Yubikey reachable
Tests whether `ssh-agent` is reachable from inside the sandbox.
Failure mode: `SSH_AUTH_SOCK` is passed through `claude-iso`'s
env whitelist but the socket file is not in
`sandbox.filesystem.allowRead`, so the agent's `ssh` /
`git push` subprocesses cannot `connect(2)` to the socket.
**Command:**
```bash
if [ -z "$SSH_AUTH_SOCK" ]; then
echo "PROBE: ssh-agent → ⊘ (SSH_AUTH_SOCK not set in env)"
elif [ ! -S "$SSH_AUTH_SOCK" ]; then
echo "PROBE: ssh-agent → ✗ (socket file at SSH_AUTH_SOCK not stat-able from inside sandbox)"
echo " SSH_AUTH_SOCK=$SSH_AUTH_SOCK"
else
ssh-add -l > /tmp/ssh-add.out 2>&1; rc=$?
case "$rc" in
0) echo "PROBE: ssh-agent → ✓ ($(wc -l < /tmp/ssh-add.out | tr -d ' ') identities listed)" ;;
1) echo "PROBE: ssh-agent → ✓ (agent reachable, no identities configured)" ;;
2) echo "PROBE: ssh-agent → ✗ (agent unreachable: $(head -1 /tmp/ssh-add.out))" ;;
*) echo "PROBE: ssh-agent → ⚠ (unexpected rc=$rc: $(head -1 /tmp/ssh-add.out))" ;;
esac
fi
```
**Interpretation:**
| Result | Status | Meaning |
|---|---|---|
| `✓ N identities listed` | Pass | `ssh-add -l` returned the key list. |
| `✓ agent reachable, no identities` | Pass | `ssh-add -l` returned rc=1 (the documented "no keys" exit). |
| `✗ socket not stat-able` | Fail | Sandbox blocks `stat(2)` on the socket file. |
| `✗ agent unreachable` | Fail | Sandbox blocks `connect(2)` to the socket. |
| `⊘ SSH_AUTH_SOCK not set` | Skip | Either the user does not run `ssh-agent`, or `claude-iso`'s env whitelist dropped it (separate bug — verify). |
**On ✗ → remediation:**
[`docs/setup/sandbox-troubleshooting.md` — SSH agent / Yubikey appears unreachable from inside the sandbox](../../../../docs/setup/sandbox-troubleshooting.md#ssh-agent--yubikey-appears-unreachable-from-inside-the-sandbox).
### Probe 2 — Localhost port bind
Tests whether a process inside the sandbox can bind to a
loopback port AND then talk to itself over loopback. The
failure mode the catalog documents is the second half (egress
proxy blocks `127.0.0.1`).
**Command:**
```bash
python3 - <<'PY' 2>&1 || true
import socket, urllib.request, threading, http.server, sys
# 1. Can we bind?
try:
s = socket.socket()
s.bind(("127.0.0.1", 0))
port = s.getsockname()[1]
s.listen(1)
except OSError as e:
print(f"PROBE: localhost-bind → ✗ (bind: {e})")
sys.exit(0)
# 2. Can we GET from our own server over loopback?
class Handler(http.server.BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200); self.end_headers(); self.wfile.write(b"ok")
def log_message(self, *_): pass
server = http.server.HTTPServer(("127.0.0.1", 0), Handler)
threading.Thread(target=server.serve_forever, daemon=True).start()
try:
with urllib.request.urlopen(f"http://127.0.0.1:{server.server_port}/", timeout=5) as r:
body = r.read()
print(f"PROBE: localhost-bind → ✓ (bound + loopback GET → HTTP {r.status}, body={body!r})")
except Exception as e:
print(f"PROBE: localhost-bind → ✗ (bind ok, loopback GET: {type(e).__name__}: {e})")
finally:
server.shutdown()
s.close()
PY
```
**Interpretation:**
| Result | Status | Meaning |
|---|---|---|
| `✓ bound + loopback GET → HTTP 200` | Pass | Both bind and loopback HTTP work. |
| `✗ bind: ...` | Fail | The sandbox blocks `bind(2)` on `127.0.0.1`. Rare. |
| `✗ bind ok, loopback GET: ...` | Fail | Bind works but the sandbox egress proxy refuses `127.0.0.1` as a destination. Common shape. |
**On ✗ → remediation:**
[`docs/setup/sandbox-troubleshooting.md` — Test cannot bind to a localhost port](../../../../docs/setup/sandbox-troubleshooting.md#test-cannot-bind-to-a-localhost-port).
### Probe 3 — Docker / Podman runtime socket
Tests whether the runtime CLI can talk to its daemon. Run for
each of `docker` / `podman` that is on `PATH`; ⊘ each that is
not installed (this is not a sandbox failure, just an absent
prerequisite).
**Command:**
```bash
for rt in docker podman; do
if ! command -v "$rt" > /dev/null 2>&1; then
echo "PROBE: ${rt}-runtime → ⊘ ($rt not on PATH)"
continue
fi
out=$("$rt" info > /dev/null 2>&1 && echo ok || echo "fail:$?")
case "$out" in
ok)
echo "PROBE: ${rt}-runtime → ✓ (${rt} info returned)" ;;
fail:*)
err=$("$rt" info 2>&1 >/dev/null | head -2 | tr '\n' ' ')
echo "PROBE: ${rt}-runtime → ✗ ($out: $err)"
;;
esac
done
```
**Interpretation:**
| Result | Status | Meaning |
|---|---|---|
| `✓ <rt> info returned` | Pass | The CLI reached the daemon successfully. |
| `✗ fail:1: Cannot connect to the Docker daemon …` | Fail | Daemon socket not readable from inside the sandbox. |
| `✗ fail:1: connect: permission denied` | Fail | Same root cause, different stderr (Linux variant). |
| `⊘ <rt> not on PATH` | Skip | Runtime not installed; not a sandbox restriction. |
**On ✗ → remediation:**
[`docs/setup/sandbox-troubleshooting.md` — Docker / Podman command fails with a socket error](../../../../docs/setup/sandbox-troubleshooting.md#docker--podman-command-fails-with-a-socket-error).
## After the report
If every probe is ✓ or ⊘:
> All three probes pass (or are not applicable). The sandbox is
> not currently blocking the known failure modes catalogued in
> `docs/setup/sandbox-troubleshooting.md`. If you hit a different
> sandbox-shaped failure, follow the catalog's *Adding a new
> entry* section and (optionally) extend this skill with a fourth
> probe so future runs catch the same shape automatically.
If any probe is ✗:
1. Surface every fail in one report (do not stop at the first).
2. For each fail, print the troubleshooting-doc anchor link from
the probe's *On ✗ → remediation* row above.
3. Suggest the user open the catalog entry to read the symptom →
root cause → fix shape, then apply the settings.json widening
themselves. Do **not** propose to apply the widening from this
skill — settings.json widenings are sandbox-bypass-adjacent
and need an explicit user-driven edit.
4. After the user has applied the widening (in a separate flow),
re-run `setup-isolated-setup-doctor` to confirm the probe now
passes.
If a probe surfaces a fail shape not catalogued in
[`docs/setup/sandbox-troubleshooting.md`](../../../../docs/setup/sandbox-troubleshooting.md):
1. Report the fail with the literal probe command + exit code +
stderr.
2. Suggest the user add a new entry to the catalog per its
*Adding a new entry* section (symptom verbatim, root cause,
fix, notes).
3. Once the catalog has the new entry, extend this skill with a
matching probe in the same shape so the next doctor run
catches it automatically.
## Extending the skill with a new probe
When the catalog grows a new entry, add a matching probe section
following the shape above:
1. **Command** — a short, deterministic, side-effect-free
one-liner (or short Python heredoc) that triggers the failure
mode reliably.
2. **Interpretation** — a 3–5-row table mapping result strings
to ✓ / ✗ / ⊘ / ⚠.
3. **On ✗ → remediation** — a direct link to the matching
section of `docs/setup/sandbox-troubleshooting.md`.
Keep probes narrowly scoped: each probe tests **one** failure
mode, not a bundle. A probe that conflates two restrictions
makes the report ambiguous when the result is ✗.