Integration test for repowire peer-to-peer messaging. Supports claudemux, opencode, or mixed-backend testing with circle boundaries and cross-backend communication.
Unified integration test for peer-to-peer communication across agent types.
| Argument | Behavior |
|---|---|
claude-code |
Test Claude Code agent type only |
opencode |
Test OpenCode agent type only |
mixed |
Test cross-agent-type communication |
all or no argument |
Run all three modes in parallel using agent teams |
When all is specified (or no argument), the lead agent should:
test-claude-code β runs claude-code teststest-opencode β runs opencode teststest-mixed β runs mixed tests (depends on both agent types being available)Use the Task tool with run_in_background: true to run modes in parallel:
Task(subagent_type="general-purpose", run_in_background=true, prompt="Run the claude-code integration test. [paste mode section]")
Task(subagent_type="general-purpose", run_in_background=true, prompt="Run the opencode integration test. [paste mode section]")
Each agent gets the full context for its mode and runs independently.
Run these commands to understand current state. Keep all phases in the same
shell so REPOWIRE_BIN continues to identify the exact binary under test.
# Pin every command in this workflow to one binary. Phase 4 replaces this with
# the freshly built checkout binary when a fresh install is requested.
REPOWIRE_BIN="$(command -v repowire || true)"
export REPOWIRE_BIN
# System checks
tmux list-sessions 2>/dev/null || echo "No tmux sessions"
which opencode 2>/dev/null || echo "opencode not in PATH"
which claude 2>/dev/null || echo "claude not in PATH"
# Daemon and peer status
curl -s http://127.0.0.1:8377/health 2>/dev/null | jq . || echo "Daemon not running"
curl -s http://127.0.0.1:8377/peers 2>/dev/null | jq '.peers[] | {name, status, circle}' || echo "No peers"
# Installation status
test -n "$REPOWIRE_BIN" && "$REPOWIRE_BIN" --version || echo "Repowire not installed"
jq -r '"Hooks: " + ((.hooks // {}) | keys | join(", "))' "$HOME/.claude/settings.json" 2>/dev/null || echo "No Claude hooks"
ls ~/.opencode/plugin/repowire.ts 2>/dev/null && echo "OpenCode plugin installed" || echo "No OpenCode plugin"
Ask user for:
all)anthropic/claude-sonnet-4-5-20250929) β needed for opencode/mixed modestmux kill-session -t circle-a 2>/dev/null || true
tmux kill-session -t circle-b 2>/dev/null || true
tmux kill-session -t opencode-test 2>/dev/null || true
tmux kill-session -t mixed-test 2>/dev/null || true
curl -s http://127.0.0.1:8377/health >/dev/null 2>&1 && {
"$REPOWIRE_BIN" peer prune --force 2>/dev/null || true
}
rm -f ~/.repowire/pending/*.json 2>/dev/null || true
rm -f ~/.repowire/sessions.json 2>/dev/null || true
IMPORTANT: repowire setup calls claude mcp add which fails inside a Claude Code session. Run these commands in a tmux pane, not from within Claude Code.
# Run in a tmux pane (e.g. tmux send-keys to a spare window):
# Build from local source
cd /path/to/repowire
mkdir -p bin
(cd daemon-go && go build -o ../bin/repowire .)
REPOWIRE_BIN="$(pwd)/bin/repowire"
export REPOWIRE_BIN
# Setup hooks and MCP server
"$REPOWIRE_BIN" setup --no-service
# Verify
"$REPOWIRE_BIN" --version
jq -r '"Hooks: " + ((.hooks // {}) | keys | join(", "))' "$HOME/.claude/settings.json"
ls ~/.opencode/plugin/repowire.ts 2>/dev/null && echo "OpenCode plugin: OK"
test -x "$REPOWIRE_BIN" || { echo "REPOWIRE_BIN is not executable: $REPOWIRE_BIN" >&2; exit 1; }
curl -s http://127.0.0.1:8377/health || {
nohup "$REPOWIRE_BIN" serve > /tmp/repowire-daemon.log 2>&1 &
sleep 3
}
curl -s http://127.0.0.1:8377/health | jq .
Requires: tmux, claude CLI, Claude hooks installed
Tmux sessions used: circle-a, circle-b
Project directories needed: 3 (2 for circle-a, 1 for circle-b)
tmux new-session -d -s circle-a -n peer-a1
tmux new-window -t circle-a -n peer-a2
tmux new-session -d -s circle-b -n peer-b1
tmux send-keys -t circle-a:peer-a1 "cd $PROJECT_A1 && claude --dangerously-skip-permissions" Enter
sleep 3
tmux send-keys -t circle-a:peer-a2 "cd $PROJECT_A2 && claude --dangerously-skip-permissions" Enter
sleep 3
tmux send-keys -t circle-b:peer-b1 "cd $PROJECT_B1 && claude --dangerously-skip-permissions" Enter
sleep 30 # Wait for sessions to initialize and hooks to register
# All 3 peers should be online with correct circles and peer_id format
curl -fsS http://127.0.0.1:8377/peers | jq -e --arg a1 "$PEER_A1" --arg a2 "$PEER_A2" --arg b1 "$PEER_B1" '
def valid($name; $circle):
[.peers[] | select(
.name == $name and
.status == "online" and
.circle == $circle and
(.peer_id | test("^repow-" + $circle + "-[a-f0-9]{8}$"))
)] | length == 1;
if valid($a1; "circle-a") and valid($a2; "circle-a") and valid($b1; "circle-b") then
[.peers[] | select(.name == $a1 or .name == $a2 or .name == $b1) |
{name, status, circle, peer_id}]
else
error("registration assertion failed: expected exactly one online peer for each name with the correct circle and peer_id")
end
'
If a peer doesn't register, check .claude/settings.local.json for disableAllHooks: true.
Direct query to peer-a1
"$REPOWIRE_BIN" peer ask $PEER_A1 "What is this project about in one sentence?" -t 120
Expected: Direct response describing the project.
Peer-to-peer proxy (peer-a1 β peer-a2 via MCP ask_peer)
"$REPOWIRE_BIN" peer ask $PEER_A1 \
"Use the ask_peer tool to ask $PEER_A2: What is this project about in one sentence? Return their exact response." \
-t 180
Expected: peer-a1 responds with peer-a2's answer.
Circle verification
curl -s http://127.0.0.1:8377/peers | jq '.peers[] | select(.circle == "circle-a") | {name, peer_id}'
Event chain
curl -s http://127.0.0.1:8377/events | jq '.[-10:]'
tmux kill-session -t circle-a 2>/dev/null || true
tmux kill-session -t circle-b 2>/dev/null || true
"$REPOWIRE_BIN" peer prune --force 2>/dev/null || true
repow-{circle}-{uuid8}Requires: opencode CLI, OpenCode plugin installed at ~/.opencode/plugin/repowire.ts
Tmux sessions used: opencode-test
Project directories needed: 2
Model flag needed: e.g., --model anthropic/claude-sonnet-4-5-20250929
session.prompt().--model flag must specify a working model. LiteLLM proxy models may return 404.tmux new-session -d -s opencode-test -n peer-1
tmux new-window -t opencode-test -n peer-2
tmux send-keys -t opencode-test:peer-1 "cd $PROJECT_1 && opencode --model $OPENCODE_MODEL" Enter
sleep 3
tmux send-keys -t opencode-test:peer-2 "cd $PROJECT_2 && opencode --model $OPENCODE_MODEL" Enter
sleep 10
# Warm up: send initial prompt to create sessions
tmux send-keys -t opencode-test:peer-1 -l "hi"
tmux send-keys -t opencode-test:peer-1 Enter
sleep 2
tmux send-keys -t opencode-test:peer-2 -l "hi"
tmux send-keys -t opencode-test:peer-2 Enter
sleep 30 # Wait for warmup to complete
curl -s http://127.0.0.1:8377/peers | jq '
[.peers[] | select(.backend == "opencode" and .status == "online") |
{name, peer_id, backend}]
'
WebSocket connection & peer discovery
curl -s http://127.0.0.1:8377/peers | jq '.peers[] | select(.backend == "opencode") | {name, status, peer_id}'
Expected: Both peers online with repow-{circle}-{uuid8} format.
Direct query to peer-1
"$REPOWIRE_BIN" peer ask $PEER_1 "What is this project about in one sentence?" -t 120
Expected: Response from the model (not "empty response" or "Not Found").
Peer-to-peer proxy (peer-1 β peer-2)
"$REPOWIRE_BIN" peer ask $PEER_1 \
"Use the ask_peer tool to ask $PEER_2: What is this project about in one sentence? Return their exact response." \
-t 180
Reverse proxy (peer-2 β peer-1)
"$REPOWIRE_BIN" peer ask $PEER_2 \
"Use the ask_peer tool to ask $PEER_1: What is this project about in one sentence? Return their exact response." \
-t 180
| Symptom | Cause | Fix |
|---|---|---|
| "No active session" | Warmup prompt didn't create session | Press Enter manually in opencode pane, or wait longer |
| "(empty response)" | Model returned 0 parts | Check model config β switch to anthropic/claude-sonnet-4-5-20250929 |
| "Not Found" in TUI | LiteLLM model ID not recognized | Use --model anthropic/<model-id> directly |
| Plugin not connecting | Wrong WS URL or old plugin | Reinstall with "$REPOWIRE_BIN" setup --no-service |
tmux kill-session -t opencode-test 2>/dev/null || true
"$REPOWIRE_BIN" peer prune --force 2>/dev/null || true
repow-{circle}-{uuid8} peer_idRequires: Both claude-code AND opencode setups working
Tmux sessions used: mixed-test
Project directories needed: 2 (1 for claude, 1 for opencode)
Depends on: claude-code and opencode modes passing individually first
tmux new-session -d -s mixed-test -n claude-peer
tmux new-window -t mixed-test -n opencode-peer
tmux send-keys -t mixed-test:claude-peer "cd $PROJECT_CLAUDE && claude --dangerously-skip-permissions" Enter
sleep 3
tmux send-keys -t mixed-test:opencode-peer "cd $PROJECT_OPENCODE && opencode --model $OPENCODE_MODEL" Enter
sleep 10
# Warm up opencode peer
tmux send-keys -t mixed-test:opencode-peer -l "hi"
tmux send-keys -t mixed-test:opencode-peer Enter
sleep 30
Cross-agent-type registration
curl -s http://127.0.0.1:8377/peers | jq '.peers[] | select(.status == "online") | {name, backend, peer_id}'
Claude β OpenCode proxy
"$REPOWIRE_BIN" peer ask $CLAUDE_PEER \
"Use ask_peer to ask $OPENCODE_PEER: What is this project about in one sentence? Return their response." \
-t 180
OpenCode β Claude proxy
"$REPOWIRE_BIN" peer ask $OPENCODE_PEER \
"Use ask_peer to ask $CLAUDE_PEER: What is this project about in one sentence? Return their response." \
-t 180
Event chain
curl -s http://127.0.0.1:8377/events | jq '.[-10:]'
tmux kill-session -t mixed-test 2>/dev/null || true
"$REPOWIRE_BIN" peer prune --force 2>/dev/null || true
repow-{circle}-{uuid8} peer_idAggregate results from all modes into a single report:
==========================================
REPOWIRE INTEGRATION TEST RESULTS
==========================================
MODE: CLAUDE-CODE
β Registration: PASS/FAIL (X/Y peers)
β Direct query: PASS/FAIL
β Proxy query: PASS/FAIL
β Status: PASS/FAIL
MODE: OPENCODE
β Registration: PASS/FAIL (X/Y peers)
β Direct query: PASS/FAIL
β Proxy query: PASS/FAIL
β Status: PASS/FAIL
MODE: MIXED
β Registration: PASS/FAIL
β ClaudeβOC: PASS/FAIL
β OCβClaude: PASS/FAIL
β Status: PASS/FAIL
Overall: PASS/FAIL
==========================================
tmux kill-session -t circle-a 2>/dev/null || true
tmux kill-session -t circle-b 2>/dev/null || true
tmux kill-session -t opencode-test 2>/dev/null || true
tmux kill-session -t mixed-test 2>/dev/null || true
"$REPOWIRE_BIN" peer prune --force 2>/dev/null || true
"$REPOWIRE_BIN" serve # Start daemon
"$REPOWIRE_BIN" peer ask NAME "q" -t 120 # Query a peer
curl -s localhost:8377/peers | jq # List peers
curl -s localhost:8377/events | jq '.[-5:]' # Recent events
"$REPOWIRE_BIN" peer prune --force # Remove offline peers