feat: daemon-based spawning for spawn CLI command#361
Conversation
The `spawn` command now communicates directly with the daemon via socket protocol, removing the requirement for the dashboard to be running. Falls back to HTTP API (dashboard) if daemon socket is unavailable. This simplifies agent orchestration - just `agent-relay up` is sufficient for spawn/release operations. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When socket is in /tmp, use /tmp/agent-relay-state/ subdirectory for teamDir. Fixes atomic write failures on macOS where temp file cleanup can remove files between write and rename operations. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Shadow options (shadowMode, shadowAgent, shadowTriggers) were silently dropped when spawning via the daemon socket path. The HTTP API fallback passed all fields via JSON.stringify, but the SDK path used explicit field listing that missed these three fields. Thread shadowMode, shadowAgent, and shadowTriggers through: - Protocol SpawnPayload type - SDK client.spawn() options and envelope payload - Daemon SpawnManager.handleSpawn() pass-through - CLI client.spawn() call Add SDK test verifying shadow options appear in sent envelope. Add sdk-daemon-parity Claude rule to prevent future field omissions. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Move daemonSpawnSucceeded and daemonReleaseSucceeded flags to before the disconnect() call. This ensures that if disconnect() throws an error, successful spawns/releases are still correctly reported rather than being treated as failures. Addresses Devin review comments on PR #361. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
| } catch (daemonErr: any) { | ||
| // If daemon connection failed, try HTTP API as fallback | ||
| if (daemonErr.message?.includes('ENOENT') || daemonErr.message?.includes('ECONNREFUSED') || daemonErr.code === 'ENOENT') { | ||
| // Socket doesn't exist or daemon not running - try HTTP API | ||
| } else if (!daemonSpawnSucceeded) { | ||
| // Other error during spawn - report it | ||
| console.error(`Failed to spawn ${name}: ${daemonErr.message}`); | ||
| process.exit(1); | ||
| } | ||
| } |
There was a problem hiding this comment.
🔴 Duplicate spawn/release attempts after disconnect error
When the daemon spawn/release operation succeeds but client.disconnect() throws an error, the code falls through to the HTTP API fallback and attempts the operation again.
Click to expand
How the bug is triggered
await client.spawn()succeeds atsrc/cli/index.ts:2418-2430daemonSpawnSucceeded = trueis set at line 2433await client.disconnect()throws an error at line 2434- The catch block is entered at line 2447
- The error message doesn't include 'ENOENT' or 'ECONNREFUSED' (it's a disconnect error)
!daemonSpawnSucceededisfalse, so the exit at line 2454 is skipped- Code falls through to HTTP API fallback at line 2458
- HTTP API tries to spawn the same agent again!
Actual vs Expected
Actual: If disconnect fails after a successful spawn, the code will attempt to spawn the agent again via HTTP API, potentially causing duplicate agents or errors.
Expected: If the spawn operation succeeded, the command should exit successfully regardless of disconnect errors.
Impact
This can cause:
- Duplicate spawn attempts for the same agent name
- Confusing error messages if the second spawn fails because the agent already exists
- Resource waste from unnecessary HTTP API calls
The same bug exists in the release command at lines 2518-2538.
Recommendation: Add process.exit(0) after the catch block when daemonSpawnSucceeded is true, or restructure the error handling to exit early when the operation succeeded. For example:
} catch (daemonErr: any) {
if (daemonSpawnSucceeded) {
// Spawn succeeded, disconnect error is non-fatal
process.exit(0);
}
// ... rest of error handling
}Was this helpful? React with 👍 or 👎 to provide feedback.
The
spawncommand now communicates directly with the daemon via socket protocol, removing the requirement for the dashboard to be running. Falls back to HTTP API (dashboard) if daemon socket is unavailable.This simplifies agent orchestration - just
agent-relay upis sufficient for spawn/release operations.