Yeachan-Heo/oh-my-codex · critical · Error
scale_down_cleanup_debt_worktree_symlink:${worker.name}
Error message
scale_down_cleanup_debt_worktree_symlink:${worker.name} What it means
The expected worktree path for a worker exists but realpath() differs from the path itself, i.e. the worktree is (or contains) a symlink. Scale-down cleanup refuses to operate through symlinks. Message embeds worker name.
Source
Thrown at src/team/scaling.ts:1609
const expectedWorktreePath = join(repoRoot, '.omx', 'team', teamName, 'worktrees', worker.name);
if (!isAbsolute(worker.worktree_path) || worker.worktree_path !== resolve(worker.worktree_path)
|| resolve(worker.worktree_path) !== expectedWorktreePath
|| (worker.worktree_repo_root !== undefined && (
!isAbsolute(worker.worktree_repo_root)
|| worker.worktree_repo_root !== resolve(worker.worktree_repo_root)
|| resolve(worker.worktree_repo_root) !== repoRoot
))
|| (worker.worktree_detached !== undefined && typeof worker.worktree_detached !== 'boolean')
|| (worker.worktree_created !== undefined && typeof worker.worktree_created !== 'boolean')
|| (worker.worktree_created === true && (
typeof worker.worktree_repo_root !== 'string' || typeof worker.worktree_detached !== 'boolean'
))) {
throw new Error(`scale_down_cleanup_debt_invalid_worktree_target:${worker.name}`);
}
await assertExistingPathParentContained(expectedWorktreePath, repoRoot);
if (existsSync(expectedWorktreePath) && await realpath(expectedWorktreePath) !== expectedWorktreePath) {
throw new Error(`scale_down_cleanup_debt_worktree_symlink:${worker.name}`);
}
}
}
async function cleanupScaleDownResources(
teamName: string,
teamStateRoot: string,
workers: readonly ScaleDownCleanupDebtResource[],
): Promise<void> {
for (const worker of workers) {
// An absent exact worktree means a prior cleanup completed before crashing.
// Do not replay a restoration or git removal against a replacement path.
if (worker.worktree_path && existsSync(worker.worktree_path)) {
await removeWorkerWorktreeRootAgentsFile(
teamName,
worker.name,
worker.team_state_root ?? teamStateRoot,
worker.worktree_path,View on GitHub (pinned to 3ad79a8a6f)
Solutions
- Replace the symlink with a real directory (or remove it and let the tool recreate the worktree)
- Avoid symlinking .omx/team/*/worktrees entries; configure worktree storage location properly instead
- Re-run scale-down after removing the link
Defensive patterns
Strategy: validation
Validate before calling
import { existsSync, realpathSync, statSync } from 'node:fs';
function worktreeIsRealDir(p: string): boolean {
return !existsSync(p) || (statSync(p).isDirectory() && realpathSync(p) === p);
} Try / catch
catch (e) {
const m = /scale_down_cleanup_debt_worktree_symlink:(.+)$/.exec(String((e as Error).message));
if (m) { await replaceSymlinkWithDir(team, m[1]); return retryScaleDown(); }
throw e;
} Prevention
- Never replace worktree directories with symlinks
- Configure storage location properly instead of linking around it
When it happens
Trigger: validateScaleDownCleanupResources finds existsSync(expectedWorktreePath) true and realpath(...) !== path, meaning a symlink was planted at the expected worktree location.
Common situations: Users symlinking worktrees to another disk for space; malicious or accidental symlink placing cleanup targets outside the repo; backup tools replacing directories with links.
Related errors
- scale_down_cleanup_debt_path_escape:${path}
- scale_down_cleanup_debt_invalid_worktree_metadata:${worker.n
- scale_down_cleanup_debt_invalid_worktree_target:${worker.nam
- run directory escapes the authorized runs root
- state directory escapes the authorized run directory
AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27).
Data as JSON: /api/errors/1e2dd1f2397fe5e8.
Report an issue: GitHub.