Skip to content

feat: recursive spawn tree passback#3016

Closed
AhmedTMM wants to merge 2 commits intoOpenRouterTeam:mainfrom
AhmedTMM:feat/tree-passback-v2
Closed

feat: recursive spawn tree passback#3016
AhmedTMM wants to merge 2 commits intoOpenRouterTeam:mainfrom
AhmedTMM:feat/tree-passback-v2

Conversation

@AhmedTMM
Copy link
Collaborator

Summary

When a session ends, the parent pulls the child VM's spawn history and merges it into local history — enabling spawn tree to show the full recursive hierarchy across VMs.

  • getParentFields() — sets parent_id and depth on all saveSpawnRecord calls using SPAWN_PARENT_ID/SPAWN_DEPTH env vars
  • pullChildHistory() — after session ends (both interactive and headless), downloads child's history.json via runner.downloadFile and merges via mergeChildHistory
  • spawn pull-history command — recursively SSHes into each active child, tells it to pull from ITS children first, then downloads history. This collapses the full tree to the root regardless of depth.
  • 11 tests for parseAndMergeChildHistory covering empty input, valid records, parent_id preservation, deduplication, connection info

Supersedes #2999 (rebuilt cleanly from upstream main).

How infinite recursion works

  1. Parent exits session → pullChildHistory(child)
  2. pullChildHistory SSHes into child, runs spawn pull-history
  3. spawn pull-history on child iterates its active children, SSHes into each, runs spawn pull-history (recurse)
  4. Each level downloads grandchild history and merges it
  5. Parent downloads child's history (which now contains the full subtree)
  6. spawn tree shows the complete hierarchy

Test plan

  • bunx @biomejs/biome check src/ — zero errors
  • bun test — 83 related tests pass
  • spawn claude sprite --beta recursive → agent spawns child → exit → spawn tree shows parent-child

🤖 Generated with Claude Code

When the interactive session ends (or headless mode completes), the
parent downloads the child VM's history.json and merges records into
local history. Before downloading, it runs `spawn pull-history` on the
child, which recursively pulls from all grandchildren — so the full
tree collapses up to the root regardless of depth.

Changes:
- Add getParentFields() — sets parent_id/depth on saveSpawnRecord calls
- Add pullChildHistory() — downloads + merges child history after session
- Add `spawn pull-history` command for recursive SSH-based history pull
- Add 11 tests for parseAndMergeChildHistory

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@AhmedTMM AhmedTMM force-pushed the feat/tree-passback-v2 branch from f6612c5 to ffe7cec Compare March 26, 2026 17:40
Copy link
Member

@louisgv louisgv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security Review

Verdict: APPROVED
Commit: 1a0820f

Findings

No critical or high-severity security issues found. All concerns are acceptable for spawn's ephemeral VM architecture:

  • MEDIUM (Acceptable) — SSH uses StrictHostKeyChecking=no (pull-history.ts:110) — Standard pattern for ephemeral cloud VMs, consistent with existing codebase
  • MEDIUM (Acceptable) — User/IP from history.json in SSH command (pull-history.ts:116) — Values are internally generated during provisioning, not user input
  • LOW — Recursive calls without explicit depth limit — Mitigated by 60s timeout per level

Security Strengths

  • ✅ Proper schema validation with valibot (ChildHistorySchema)
  • ✅ No type assertions (as keyword) — follows CLAUDE.md guidelines
  • ✅ Command injection prevention — all remote commands are hardcoded strings
  • ✅ Path traversal prevention — tmpPath uses generated UUIDs
  • ✅ Proper error handling with asyncTryCatch
  • ✅ Resource cleanup (unlinkSync on temp files)
  • ✅ Timeout protection (60s recursive pull, 30s file download)

Tests

  • ✅ bun test: 14 tests PASS (0 failures)
  • ✅ biome lint: 0 errors
  • ✅ Comprehensive coverage: edge cases, deduplication, validation, error handling

Code Quality

  • Follows ESM-only pattern (no require/CommonJS)
  • Proper TypeScript types with valibot validation
  • Consistent with spawn's orchestration patterns
  • Well-documented with inline comments

-- security/pr-reviewer

@louisgv
Copy link
Member

louisgv commented Mar 26, 2026

The PR branch needs to be updated with the latest main branch before it can be merged. Please run:

git fetch upstream main
git merge upstream/main
git push origin HEAD

Once updated, the PR will be ready to merge.

@la14-1 la14-1 closed this Mar 26, 2026
@la14-1 la14-1 reopened this Mar 26, 2026
@la14-1
Copy link
Member

la14-1 commented Mar 26, 2026

Closing and reopening to sync with force-pushed branch

@la14-1 la14-1 closed this Mar 26, 2026
@la14-1 la14-1 reopened this Mar 26, 2026
@la14-1
Copy link
Member

la14-1 commented Mar 26, 2026

Replacing with a new PR - GitHub stuck on stale head SHA after force-push conflict resolution.

@la14-1 la14-1 closed this Mar 26, 2026
@la14-1 la14-1 mentioned this pull request Mar 26, 2026
3 tasks
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.

3 participants