mirror of
https://github.com/stablyai/orca.git
synced 2026-10-17 08:03:14 +00:00
fix(runtime): stop git-classifying folder workspaces during session hydration (root cause of #8283)
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -97,6 +97,8 @@ import {
|
||||
getDefaultWorkspaceSession
|
||||
} from '../../../../shared/constants'
|
||||
import { folderWorkspaceKey, worktreeWorkspaceKey } from '../../../../shared/workspace-scope'
|
||||
import { isGitRepoKind } from '../../../../shared/repo-kind'
|
||||
import { FOLDER_WORKSPACE_INSTANCE_SEPARATOR } from '../../../../shared/worktree-id'
|
||||
import {
|
||||
createTestStore,
|
||||
makeLayout,
|
||||
@@ -288,6 +290,46 @@ describe('hydrateWorkspaceSession', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('does not synthesize a git placeholder from a runtime folder-workspace instance id (#8283)', () => {
|
||||
const store = createTestStore()
|
||||
// A folder (non-git) project's workspace-instance id keeps the runtime
|
||||
// shape `folder-workspace:<pgid>::<folderPath>::workspace:<uuid>`. Since it
|
||||
// carries no `worktree:`/`folder:` prefix, the placeholder builder's folder
|
||||
// guard misses it, and splitWorktreeId (first `::`) leaves the
|
||||
// `::workspace:<uuid>` suffix on the derived path.
|
||||
const folderRepoId = 'folder-workspace:pg1'
|
||||
const folderPath = '/home/user'
|
||||
const worktreeId = `${folderRepoId}::${folderPath}${FOLDER_WORKSPACE_INSTANCE_SEPARATOR}11111111-1111-1111-1111-111111111111`
|
||||
const session: WorkspaceSessionState = {
|
||||
...getDefaultWorkspaceSession(),
|
||||
activeRepoId: folderRepoId,
|
||||
activeWorktreeId: worktreeId,
|
||||
activeTabId: 'folder-instance-tab',
|
||||
tabsByWorktree: {
|
||||
[worktreeId]: [
|
||||
makeTab({ id: 'folder-instance-tab', worktreeId, ptyId: 'folder-instance-session' })
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
store.getState().hydrateWorkspaceSession(session, {
|
||||
runtimeHostIdByWorkspaceSessionKey: { [worktreeId]: 'runtime:env-1' }
|
||||
})
|
||||
|
||||
// The synthetic `::workspace:<uuid>` suffix is not a filesystem path; if it
|
||||
// reaches a worktree path it becomes a Git cwd and fails with a continuous
|
||||
// `spawn git ENOENT` on every status poll (#8283).
|
||||
for (const worktree of store.getState().worktreesByRepo[folderRepoId] ?? []) {
|
||||
expect(worktree.path).not.toContain(FOLDER_WORKSPACE_INSTANCE_SEPARATOR)
|
||||
}
|
||||
// The placeholder repo must be classified as a folder repo so the
|
||||
// isGitRepoKind-gated status poll never spawns Git for it.
|
||||
const placeholderRepo = store.getState().repos.find((repo) => repo.id === folderRepoId)
|
||||
if (placeholderRepo) {
|
||||
expect(isGitRepoKind(placeholderRepo)).toBe(false)
|
||||
}
|
||||
})
|
||||
|
||||
it('moves restored active focus from a dead split leaf to a pty-backed sibling', () => {
|
||||
const store = createTestStore()
|
||||
const worktreeId = 'repo1::/wt-1'
|
||||
|
||||
@@ -34,7 +34,11 @@ import {
|
||||
parsePaneKey
|
||||
} from '../../../../shared/stable-pane-id'
|
||||
import { isValidHostTerminalTabId, isValidTerminalTabId } from '../../../../shared/terminal-tab-id'
|
||||
import { getRepoIdFromWorktreeId, splitWorktreeId } from '../../../../shared/worktree-id'
|
||||
import {
|
||||
getRepoIdFromWorktreeId,
|
||||
splitWorktreeId,
|
||||
splitWorktreeIdForFilesystem
|
||||
} from '../../../../shared/worktree-id'
|
||||
import { isWslUncPath } from '../../../../shared/wsl-paths'
|
||||
import type { ProjectExecutionRuntimeResolution } from '../../../../shared/project-execution-runtime'
|
||||
import type { StartupCommandDelivery } from '../../../../shared/codex-startup-delivery'
|
||||
@@ -172,6 +176,18 @@ function buildRuntimeSessionPlaceholders({
|
||||
if (!parsed) {
|
||||
continue
|
||||
}
|
||||
// Why (#8283): folder (non-git) workspace instances keep the runtime shape
|
||||
// `folder-workspace:<pgid>::<folderPath>::workspace:<uuid>` and carry no
|
||||
// `worktree:`/`folder:` prefix, so the folder guard above misses them and
|
||||
// splitWorktreeId (first `::`) leaves the `::workspace:<uuid>` suffix on the
|
||||
// path. That synthetic, nonexistent path reaches Git as a cwd and fails with
|
||||
// a continuous `spawn git ENOENT` on every status poll. Resolve the real
|
||||
// folder path and tag the placeholder as a folder repo so the
|
||||
// isGitRepoKind-gated status poll never spawns Git for it. The suffix-strip
|
||||
// itself (not a substring match) is the folder-instance signal.
|
||||
const worktreePath =
|
||||
splitWorktreeIdForFilesystem(worktreeId)?.worktreePath ?? parsed.worktreePath
|
||||
const isFolderWorkspace = worktreePath !== parsed.worktreePath
|
||||
const existingRepo = nextRepos.some((repo) => repo.id === parsed.repoId)
|
||||
if (!existingRepo) {
|
||||
// Why: remote catalogs load after hydration, but host-split session
|
||||
@@ -181,12 +197,13 @@ function buildRuntimeSessionPlaceholders({
|
||||
...nextRepos,
|
||||
{
|
||||
id: parsed.repoId,
|
||||
path: parsed.worktreePath,
|
||||
displayName: getPathDisplayName(parsed.worktreePath, parsed.repoId),
|
||||
path: worktreePath,
|
||||
displayName: getPathDisplayName(worktreePath, parsed.repoId),
|
||||
badgeColor: DEFAULT_REPO_BADGE_COLOR,
|
||||
addedAt: 0,
|
||||
connectionId: null,
|
||||
executionHostId: hostId
|
||||
executionHostId: hostId,
|
||||
...(isFolderWorkspace ? { kind: 'folder' as const } : {})
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -198,7 +215,7 @@ function buildRuntimeSessionPlaceholders({
|
||||
id: worktreeId,
|
||||
repoId: parsed.repoId,
|
||||
hostId,
|
||||
displayName: getPathDisplayName(parsed.worktreePath, parsed.repoId),
|
||||
displayName: getPathDisplayName(worktreePath, parsed.repoId),
|
||||
comment: '',
|
||||
linkedIssue: null,
|
||||
linkedPR: null,
|
||||
@@ -210,7 +227,7 @@ function buildRuntimeSessionPlaceholders({
|
||||
isPinned: false,
|
||||
sortOrder: 0,
|
||||
lastActivityAt: 0,
|
||||
path: parsed.worktreePath,
|
||||
path: worktreePath,
|
||||
head: '',
|
||||
branch: '',
|
||||
isBare: false,
|
||||
|
||||
Reference in New Issue
Block a user