Skip to content

fix: unblock friendly-battle open and LAN bind#56

Merged
ThunderConch merged 3 commits into
masterfrom
fix/friendly-battle-cwd-advertise-nowait
Apr 20, 2026
Merged

fix: unblock friendly-battle open and LAN bind#56
ThunderConch merged 3 commits into
masterfrom
fix/friendly-battle-cwd-advertise-nowait

Conversation

@ThunderConch
Copy link
Copy Markdown
Owner

Summary

Three stacked bugs made /tkm:friendly-battle open unusable whenever the host's Claude Code session ran from a cwd outside the plugin directory and wanted to accept LAN guests. This PR fixes all three with minimal churn (3 files, +103/-31).

  • tsx ERR_MODULE_NOT_FOUND on daemon spawn — detached daemon was spawned via node --import tsx <daemon.ts>, inheriting Claude Code's cwd. Node ESM's --import tsx resolution is cwd-relative, so the child crashed with Cannot find package 'tsx' imported from <user's project dir>. Fix: pin spawn() cwd to PLUGIN_ROOT (derived from import.meta.url).
  • 0.0.0.0 wildcard bind rejectedtcp-direct.ts requires an advertiseHost whenever the listen host is a wildcard, but there was no CLI flag to supply one. Fix: add --join-host flag on friendly-battle-turn.ts, thread advertiseHost through DaemonOptions into createFriendlyBattleSpikeHost, and update the LAN default in SKILL.md to --listen-host 0.0.0.0 --join-host "$LAN_IP".
  • Host Claude Code session blocked in a 5-minute foreground polling loop after open, waiting for the guest to join. Fix: remove the polling loop from Step 1a (host) and Step 1b (guest). Each flow now runs a single non-blocking --status probe, then detaches. A new /tkm:friendly-battle resume subcommand (Step 1c) picks up the turn loop when the peer is connected, and a stand-by rule lets free-form check keywords (확인, check, resume, 왔어, 접속, etc.) trigger an implicit resume.

Files touched:

  • src/cli/friendly-battle-turn.ts
  • src/friendly-battle/daemon.ts
  • skills/friendly-battle/SKILL.md

Derived from the post-mortem in report.md.

Test plan

  • tsc --noEmit — clean
  • node --import tsx --test test/friendly-battle-*.test.ts — 115/115 passing across 21 suites
  • Two independent critic reviews — zero P0/P1 findings on full diff
  • Live LAN two-machine run: host binds 0.0.0.0, guest connects via advertised LAN IP, resume on host enters turn loop
  • Live same-machine loopback run (open local + join) — confirm no regression
  • Live cwd-outside-plugin run: verify daemon starts without ERR_MODULE_NOT_FOUND

🤖 Generated with Claude Code

ThunderConch and others added 3 commits April 20, 2026 16:56
Three stacked issues made `/tkm:friendly-battle open` unusable when the
host's Claude Code session runs outside the plugin directory and wants
to accept LAN guests.

- Daemon spawn inherits Claude Code's cwd, which breaks Node ESM's
  cwd-relative `--import tsx` resolution. Pin the spawn cwd to
  PLUGIN_ROOT (derived from import.meta.url) so the detached daemon
  always finds tsx in the plugin's node_modules.
- The TCP transport rejects wildcard listen hosts (0.0.0.0 / ::) unless
  an advertise host is provided. Thread a new `--join-host` flag from
  the CLI through DaemonOptions into createFriendlyBattleSpikeHost so
  LAN mode can bind 0.0.0.0 and still publish a concrete address.
- Remove the 5-minute foreground polling loop that kept the host's
  Claude Code session blocked after open. Both `open` and `join` now
  run a single non-blocking --status probe, then detach. A new
  `/tkm:friendly-battle resume` subcommand (Step 1c) picks up the turn
  loop once the peer is connected, and a stand-by rule lets free-form
  check keywords trigger an implicit resume.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…LI regression tests

PR #56 review flagged that validateHostArg was only wired to --join-host.
Thread it through --listen-host (host init) and --host (join init) so all
three host-shaped flags fail the same way with a clean REASON: line
before we spawn a daemon or open a socket.

Also add `test/friendly-battle-turn-host-validation.test.ts` with three
regression scenarios:
- malformed value on each of --listen-host / --join-host / --host is
  rejected with a uniform REASON: ... must match … message;
- wildcard --listen-host 0.0.0.0 with --join-host 10.77.77.1 writes the
  advertised host into the session record (not the wildcard);
- the CLI invoked via bin/run-friendly-battle-turn.sh from a cwd
  outside the plugin root still reaches DAEMON_READY (emits PORT:) and
  never produces `Cannot find package 'tsx'` / ERR_MODULE_NOT_FOUND.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…sanitizer

Two findings from the adversarial review on PR #56.

[high] The stand-by/resume flow is no longer orphaned when conversational
memory drops the `sessionId`.
- New `--list-active --generation <gen>` subcommand in
  `src/cli/friendly-battle-turn.ts` enumerates session records whose
  daemon PID is still alive and whose phase is non-terminal, emitting a
  compact JSON array sorted by `updatedAt` descending. Dead daemons and
  finished/aborted records are filtered out.
- `skills/friendly-battle/SKILL.md` Step 1c now calls `--list-active`
  when `sessionId` is missing from conversation scope: empty → tell the
  user to `open`/`join` again; exactly one → adopt it; multiple →
  AskUserQuestion to pick. The existing `--status` probe runs after
  sessionId is resolved.

[medium] Renamed `validateHostArg` → `sanitizeHostArg` to match what it
actually does. The helper was never semantic host validation — it was
always a shell-safety character filter; the old name/comment/REASON
copy overclaimed. Updated the doc comment and REASON message to say
"contains characters outside the shell-safe set" so malformed-but-clean
hosts like `::::` or `[::1` that Node's net layer still rejects are
honestly described as deferred-to-transport rather than pre-validated
at the CLI.

Tests: extended `test/friendly-battle-turn-host-validation.test.ts`
with two `--list-active` cases (empty sessions dir → `[]`; live
init-host run → one entry whose sessionId/role/phase matches). The new
cases use a SIGKILL-then-poll helper so the detached daemon is reaped
before the temp dir is removed, avoiding an ENOTEMPTY race.

Full suite: 1210/1210 passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ThunderConch ThunderConch merged commit b252326 into master Apr 20, 2026
2 checks passed
@ThunderConch ThunderConch deleted the fix/friendly-battle-cwd-advertise-nowait branch April 20, 2026 13:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant