fix(lanes): wait for worktree provisioning before auto-setup
POST /worktree answers with 202 before the background git worktree add finishes, so profileInit/agentsInstall/mcpSync were racing the lane's own directory into existence and mostly failing. Poll GET /api/lanes/:id until provisioning leaves "provisioning" first.
This commit is contained in:
@@ -5,9 +5,10 @@
|
||||
* dashboard provisions a managed git worktree via `POST /api/lanes/worktree`
|
||||
* — the dashboard invents the lane's own directory and branch name, the same
|
||||
* way Shipyard's "+ Add lane" never asks a human to name a folder. The lane
|
||||
* returned is `status: "provisioning"`; the existing `lane_update` WebSocket
|
||||
* subscription in the Workspace page flips it to idle when the worktree is
|
||||
* actually ready, so this component does not poll.
|
||||
* returned is `status: "provisioning"`: the route answers before the actual
|
||||
* `git worktree add` runs, so this component polls `GET /api/lanes/:id`
|
||||
* until that finishes before running the auto-setup calls against a cwd
|
||||
* that must actually exist on disk first.
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
|
||||
@@ -19,6 +20,22 @@ import { api } from "../../lib/api";
|
||||
import type { CwdSuggestion } from "../../lib/api";
|
||||
import type { Lane } from "../../lib/types";
|
||||
|
||||
/** Polls the lane until the background `git worktree add` finishes (status
|
||||
* leaves "provisioning"), or gives up after `timeoutMs`. Returns the final
|
||||
* lane record, or `null` on timeout. */
|
||||
async function waitForProvisioned(
|
||||
laneId: number,
|
||||
{ intervalMs = 500, timeoutMs = 30000 } = {}
|
||||
): Promise<Lane | null> {
|
||||
const deadline = Date.now() + timeoutMs;
|
||||
for (;;) {
|
||||
const { lane } = await api.lanes.get(laneId);
|
||||
if (lane.status !== "provisioning") return lane;
|
||||
if (Date.now() >= deadline) return null;
|
||||
await new Promise((r) => window.setTimeout(r, intervalMs));
|
||||
}
|
||||
}
|
||||
|
||||
export function AddLaneModal({
|
||||
open,
|
||||
onClose,
|
||||
@@ -102,6 +119,19 @@ export function AddLaneModal({
|
||||
title: name,
|
||||
base: base || undefined,
|
||||
});
|
||||
onAdded(result.lane);
|
||||
|
||||
// POST /worktree returns as soon as the DB row exists (202) — the
|
||||
// actual `git worktree add` runs afterward, in the background, on the
|
||||
// server. Firing setup against the lane's cwd before that finishes
|
||||
// means agents/mcp write into a directory that doesn't exist yet, so
|
||||
// wait for provisioning to leave the "provisioning" status first.
|
||||
const provisionedLane = await waitForProvisioned(result.lane.id);
|
||||
if (!provisionedLane || provisionedLane.status === "failed") {
|
||||
setBusy(false);
|
||||
setError(t("addLaneProvisionFailed"));
|
||||
return;
|
||||
}
|
||||
|
||||
const [profileOutcome, agentsOutcome, mcpOutcome] = await Promise.allSettled([
|
||||
api.lanes.profileInit(result.lane.id),
|
||||
@@ -132,7 +162,6 @@ export function AddLaneModal({
|
||||
// user dismisses it themselves (Cancel/X) once they've seen it, rather
|
||||
// than racing a timer that can close before they've looked at it.
|
||||
setBusy(false);
|
||||
onAdded(result.lane);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : String(err));
|
||||
setBusy(false);
|
||||
|
||||
Reference in New Issue
Block a user