mirror of
https://github.com/OpenRouterTeam/spawn.git
synced 2026-04-28 03:49:31 +00:00
fix: add unique spawn IDs to prevent history record corruption (#2235)
* fix: add unique spawn IDs to prevent history record corruption
History records were matched by heuristic ("most recent record for this
cloud without a connection"), which caused saveVmConnection and
saveLaunchCmd to overwrite the wrong record during concurrent or failed
spawns.
Fix: every SpawnRecord now has a unique `id` (UUID). All history
operations (saveVmConnection, saveLaunchCmd, removeRecord,
markRecordDeleted, mergeLastConnection) match by id when available,
falling back to the old heuristic for pre-migration records.
The orchestrator (TS path) now creates the history record AFTER server
creation succeeds, not before — so failed provisions don't leave orphan
entries.
Also adds "Remove from history" option to the spawn ls action picker,
restoring the ability to soft-delete entries without destroying the VM.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add 18 unit tests for spawn ID history behavior
Tests cover:
- generateSpawnId returns unique UUIDs
- saveSpawnRecord auto-generates id when not provided
- saveVmConnection matches by spawnId (not heuristic)
- saveVmConnection does not cross-contaminate concurrent spawns
- saveVmConnection falls back to heuristic without spawnId
- saveLaunchCmd matches by spawnId (not heuristic)
- saveLaunchCmd falls back without spawnId
- removeRecord matches by id, not by timestamp+agent+cloud
- removeRecord handles duplicate timestamps correctly
- removeRecord falls back for legacy records without id
- markRecordDeleted targets correct record by id
- mergeLastConnection uses spawn_id from last-connection.json
- mergeLastConnection falls back to heuristic without spawn_id
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* style: enable biome import sorting with grouped imports
Adds organizeImports to biome assist config with groups:
1. Type imports
2. Node built-ins
3. Third-party packages
4. @openrouter/* packages
5. Aliases
Auto-fixed import order and lint issues across all TypeScript files,
including .claude/skills/ and packages/cli/src/.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
699df354a9
commit
65a81edc57
118 changed files with 1951 additions and 1780 deletions
|
|
@ -1,39 +1,39 @@
|
|||
// hetzner/hetzner.ts — Core Hetzner Cloud provider: API, auth, SSH, provisioning
|
||||
|
||||
import { mkdirSync, readFileSync } from "node:fs";
|
||||
|
||||
import {
|
||||
logInfo,
|
||||
logWarn,
|
||||
logError,
|
||||
logStep,
|
||||
logStepInline,
|
||||
logStepDone,
|
||||
prompt,
|
||||
jsonEscape,
|
||||
getSpawnCloudConfigPath,
|
||||
loadApiToken,
|
||||
validateServerName,
|
||||
validateRegionName,
|
||||
toKebabCase,
|
||||
defaultSpawnName,
|
||||
sanitizeTermValue,
|
||||
selectFromList,
|
||||
} from "../shared/ui";
|
||||
import type { CloudInitTier } from "../shared/agents";
|
||||
import { getPackagesForTier, needsNode, needsBun, NODE_INSTALL_CMD } from "../shared/cloud-init";
|
||||
|
||||
import { mkdirSync, readFileSync } from "node:fs";
|
||||
import { saveVmConnection } from "../history.js";
|
||||
import { getPackagesForTier, NODE_INSTALL_CMD, needsBun, needsNode } from "../shared/cloud-init";
|
||||
import { parseJsonObj } from "../shared/parse";
|
||||
import {
|
||||
killWithTimeout,
|
||||
SSH_BASE_OPTS,
|
||||
SSH_INTERACTIVE_OPTS,
|
||||
sleep,
|
||||
waitForSsh as sharedWaitForSsh,
|
||||
killWithTimeout,
|
||||
sleep,
|
||||
spawnInteractive,
|
||||
} from "../shared/ssh";
|
||||
import { ensureSshKeys, getSshFingerprint, getSshKeyOpts } from "../shared/ssh-keys";
|
||||
import { parseJsonObj } from "../shared/parse";
|
||||
import { isString, isNumber, toObjectArray, toRecord } from "../shared/type-guards";
|
||||
import { saveVmConnection } from "../history.js";
|
||||
import { isNumber, isString, toObjectArray, toRecord } from "../shared/type-guards";
|
||||
import {
|
||||
defaultSpawnName,
|
||||
getSpawnCloudConfigPath,
|
||||
jsonEscape,
|
||||
loadApiToken,
|
||||
logError,
|
||||
logInfo,
|
||||
logStep,
|
||||
logStepDone,
|
||||
logStepInline,
|
||||
logWarn,
|
||||
prompt,
|
||||
sanitizeTermValue,
|
||||
selectFromList,
|
||||
toKebabCase,
|
||||
validateRegionName,
|
||||
validateServerName,
|
||||
} from "../shared/ui";
|
||||
|
||||
const HETZNER_API_BASE = "https://api.hetzner.cloud/v1";
|
||||
const HETZNER_DASHBOARD_URL = "https://console.hetzner.cloud/";
|
||||
|
|
@ -428,7 +428,16 @@ export async function createServer(
|
|||
}
|
||||
|
||||
logInfo(`Server created: ID=${hetznerServerId}, IP=${hetznerServerIp}`);
|
||||
saveVmConnection(hetznerServerIp, "root", hetznerServerId, name, "hetzner");
|
||||
saveVmConnection(
|
||||
hetznerServerIp,
|
||||
"root",
|
||||
hetznerServerId,
|
||||
name,
|
||||
"hetzner",
|
||||
undefined,
|
||||
undefined,
|
||||
process.env.SPAWN_ID || undefined,
|
||||
);
|
||||
}
|
||||
|
||||
// ─── SSH Execution ───────────────────────────────────────────────────────────
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue