From 7cc2e6d83003eae16b07ef9a922192e765fa4000 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 1 Aug 2026 22:55:13 +0800 Subject: [PATCH] fix(ci): repair full release validation regressions (#117447) * fix(worktrees): reject repositories without commits * test(memory-wiki): allow agent isolation runtime * test(ios): await queued approval retry --- apps/ios/Tests/NodeAppModelInvokeTests.swift | 4 ++-- .../src/agent-vault-isolation.test.ts | 2 +- src/agents/worktrees/service.ts | 8 ++++++- src/gateway/server-methods/sessions-create.ts | 10 ++++++++- src/gateway/server.sessions.create.test.ts | 22 +++++++++++++++++++ 5 files changed, 41 insertions(+), 5 deletions(-) diff --git a/apps/ios/Tests/NodeAppModelInvokeTests.swift b/apps/ios/Tests/NodeAppModelInvokeTests.swift index c3082fa10b54..2b2355ebc780 100644 --- a/apps/ios/Tests/NodeAppModelInvokeTests.swift +++ b/apps/ios/Tests/NodeAppModelInvokeTests.swift @@ -1684,9 +1684,9 @@ private func overrideNotificationServingPreference(_ enabled: Bool) -> () -> Voi } appModel._test_setUnifiedExecApprovalGetResponse(makePendingExecApprovalJSON(approvalID)) await appModel._test_reconcileWatchExecApprovalCache(reason: "operator_reconnected") - let deadline = ContinuousClock().now.advanced(by: .seconds(2)) + let deadline = ContinuousClock().now.advanced(by: .seconds(10)) while await !writeGate.hasStarted(), ContinuousClock().now < deadline { - await Task.yield() + try await Task.sleep(for: .milliseconds(10)) } let writeCount = await writeGate.callCount() #expect(writeCount == 1) diff --git a/extensions/memory-wiki/src/agent-vault-isolation.test.ts b/extensions/memory-wiki/src/agent-vault-isolation.test.ts index 0ec60475fb55..ab3f608caadf 100644 --- a/extensions/memory-wiki/src/agent-vault-isolation.test.ts +++ b/extensions/memory-wiki/src/agent-vault-isolation.test.ts @@ -263,5 +263,5 @@ describe("agent-scoped memory-wiki tools", () => { } finally { clearMemoryPluginState(); } - }, 240_000); + }, 360_000); }); diff --git a/src/agents/worktrees/service.ts b/src/agents/worktrees/service.ts index 16171b80f342..bfb0422607ac 100644 --- a/src/agents/worktrees/service.ts +++ b/src/agents/worktrees/service.ts @@ -76,6 +76,8 @@ export class WorktreeSnapshotError extends Error { this.snapshotError = snapshotError; } } + +export class WorktreeRepositoryError extends Error {} const SNAPSHOT_REF_PREFIX = "refs/openclaw/snapshots"; const log = createSubsystemLogger("agents/worktrees"); @@ -181,9 +183,13 @@ async function resolveRepositoryFromRealPath( ): Promise { const rootResult = await runGit(requested, ["rev-parse", "--show-toplevel"]); if (rootResult.code !== 0) { - throw new Error(`not a git checkout: ${requestedLabel}`); + throw new WorktreeRepositoryError(`not a git checkout: ${requestedLabel}`); } const sourceRoot = await fs.realpath(rootResult.stdout.trim()); + const headResult = await runGit(sourceRoot, ["rev-parse", "--verify", "HEAD^{commit}"]); + if (headResult.code !== 0) { + throw new WorktreeRepositoryError(`git checkout has no commits: ${requestedLabel}`); + } const commonRaw = await requireGit(sourceRoot, ["rev-parse", "--git-common-dir"]); const commonDir = await fs.realpath( path.isAbsolute(commonRaw) ? commonRaw : path.resolve(sourceRoot, commonRaw), diff --git a/src/gateway/server-methods/sessions-create.ts b/src/gateway/server-methods/sessions-create.ts index dc246bc0c826..de06f1ce15d3 100644 --- a/src/gateway/server-methods/sessions-create.ts +++ b/src/gateway/server-methods/sessions-create.ts @@ -15,7 +15,7 @@ import { resolveSandboxRuntimeStatus } from "../../agents/sandbox/runtime-status import { ensureAgentWorkspace } from "../../agents/workspace.js"; import { insideGitCheckout } from "../../agents/worktrees/git.js"; import { slugifyWorktreeTitle } from "../../agents/worktrees/name.js"; -import { managedWorktrees } from "../../agents/worktrees/service.js"; +import { managedWorktrees, WorktreeRepositoryError } from "../../agents/worktrees/service.js"; import { resolveAgentMainSessionKey } from "../../config/sessions/main-session.js"; import { sessionEntryForkedFromParent } from "../../config/sessions/session-entry-lineage.js"; import type { SessionEntry } from "../../config/sessions/types.js"; @@ -375,6 +375,14 @@ export const sessionCreateHandlers: GatewayRequestHandlers = { provisionedSessionWorktree = true; } } catch (error) { + if (error instanceof WorktreeRepositoryError) { + respond( + false, + undefined, + errorShape(ErrorCodes.INVALID_REQUEST, "agent workspace is not a git checkout"), + ); + return; + } respond(false, undefined, errorShape(ErrorCodes.UNAVAILABLE, formatErrorMessage(error))); return; } diff --git a/src/gateway/server.sessions.create.test.ts b/src/gateway/server.sessions.create.test.ts index 2cc1e02386d9..86c182c4aac8 100644 --- a/src/gateway/server.sessions.create.test.ts +++ b/src/gateway/server.sessions.create.test.ts @@ -1405,6 +1405,28 @@ test("sessions.create rejects worktrees for non-git agent workspaces", async () } }); +test("sessions.create rejects worktrees for agent workspaces without a commit", async () => { + const workspace = await makeNonGitTempDir("openclaw-session-unborn-workspace-"); + await execFileAsync("git", ["init", workspace]); + testState.agentConfig = { workspace }; + await createSessionStoreDir(); + try { + const created = await directSessionReq( + "sessions.create", + { agentId: "main", worktree: true }, + { client: { connect: { scopes: ["operator.admin"] } } as never }, + ); + + expect(created.ok).toBe(false); + expect(created.error).toMatchObject({ + code: "INVALID_REQUEST", + message: "agent workspace is not a git checkout", + }); + } finally { + testState.agentConfig = undefined; + } +}); + test("sessions.create stores dashboard model, thinking, and parent linkage, and creates a transcript", async () => { const { storePath } = await createSessionStoreDir(); agentDiscoveryMock.enabled = true;