feat: recursive spawn tree passback#3016
Closed
AhmedTMM wants to merge 2 commits intoOpenRouterTeam:mainfrom
Closed
feat: recursive spawn tree passback#3016AhmedTMM wants to merge 2 commits intoOpenRouterTeam:mainfrom
AhmedTMM wants to merge 2 commits intoOpenRouterTeam:mainfrom
Conversation
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>
f6612c5 to
ffe7cec
Compare
louisgv
approved these changes
Mar 26, 2026
Member
louisgv
left a comment
There was a problem hiding this comment.
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 (
askeyword) — 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
Member
|
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 HEADOnce updated, the PR will be ready to merge. |
Member
|
Closing and reopening to sync with force-pushed branch |
Member
|
Replacing with a new PR - GitHub stuck on stale head SHA after force-push conflict resolution. |
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
When a session ends, the parent pulls the child VM's spawn history and merges it into local history — enabling
spawn treeto show the full recursive hierarchy across VMs.getParentFields()— setsparent_idanddepthon allsaveSpawnRecordcalls usingSPAWN_PARENT_ID/SPAWN_DEPTHenv varspullChildHistory()— after session ends (both interactive and headless), downloads child's history.json viarunner.downloadFileand merges viamergeChildHistoryspawn pull-historycommand — 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.parseAndMergeChildHistorycovering empty input, valid records, parent_id preservation, deduplication, connection infoSupersedes #2999 (rebuilt cleanly from upstream main).
How infinite recursion works
pullChildHistory(child)pullChildHistorySSHes into child, runsspawn pull-historyspawn pull-historyon child iterates its active children, SSHes into each, runsspawn pull-history(recurse)spawn treeshows the complete hierarchyTest plan
bunx @biomejs/biome check src/— zero errorsbun test— 83 related tests passspawn claude sprite --beta recursive→ agent spawns child → exit →spawn treeshows parent-child🤖 Generated with Claude Code