nexu-io/open-design · error · FinalizePackageLockedError
CONFLICT
CONFLICT
Error message
finalize is already in progress for project ${projectId} What it means
FinalizePackageLockedError: fs.openSync(lockPath, 'wx') failed with EEXIST. A `.finalize.lock` file already exists in the project directory, meaning another finalize is running, or a previous run crashed without cleaning up the lock. The route maps this to 409 CONFLICT.
Source
Thrown at apps/daemon/src/design/finalize-design.ts:304
// For native projects, it lazily creates `.od/projects/<id>`.
fs.mkdirSync(dir, { recursive: true });
const finalPath = path.join(dir, OUTPUT_FILENAME);
const lockPath = path.join(dir, LOCK_FILENAME);
const tmpPath = path.join(
dir,
`${OUTPUT_FILENAME}.tmp.${process.pid}.${randomBytes(4).toString('hex')}`,
);
const now = options.now ?? (() => new Date());
const protocol = options.protocol ?? 'anthropic';
const baseUrl = options.baseUrl ?? defaultBaseUrlForFinalizeProtocol(protocol);
const maxTokens = options.maxTokens ?? DEFAULT_MAX_TOKENS;
let lockFd: number | null = null;
try {
lockFd = fs.openSync(lockPath, 'wx');
} catch (err: unknown) {
if ((err as NodeJS.ErrnoException)?.code === 'EEXIST') {
throw new FinalizePackageLockedError(
`finalize is already in progress for project ${projectId}`,
);
}
throw err;
}
try {
// Phase 3: export transcript via the PR #493 primitive. Returns the
// disk path; we read the body and run it through the truncation
// policy so a 4 MB transcript does not blow Anthropic's context.
const transcriptResult = exportProjectTranscript(db, projectsRoot, projectId, { now });
const transcriptJsonl = fs.readFileSync(transcriptResult.path, 'utf8');
const truncatedJsonl = truncateTranscriptForPrompt(transcriptJsonl);
// Phase 4: design system. Project may not have one selected; readDesignSystem
// returns null on missing DESIGN.md so the prompt's design-system section
// gracefully falls back to "(no design system selected for this project)".
const designSystemId =View on GitHub (pinned to 5be4028344)
Solutions
- Wait for the in-progress finalize to complete, then retry.
- If no finalize is running (confirm via process list / a second request still returning 409), remove the stale lock: `rm <projectDir>/.finalize.lock`.
- Avoid triggering concurrent finalize on the same project from multiple tabs.
Defensive patterns
Strategy: try-catch
Validate before calling
// Optional pre-check: only helps if no concurrent finalize is known.
import { existsSync } from 'node:fs';
import { join } from 'node:path';
const lockPath = join(projectDir, '.finalize.lock');
if (existsSync(lockPath)) {
// Either wait or prompt the operator to clear a stale lock.
} Type guard
import { FinalizePackageLockedError } from './finalize-design.js';
function isFinalizeLocked(err: unknown): err is FinalizePackageLockedError {
return err instanceof FinalizePackageLockedError;
} Try / catch
import { FinalizePackageLockedError } from './finalize-design.js';
try {
await finalizeDesignPackage(db, projectsRoot, dsRoot, projectId, options);
} catch (err) {
if (err instanceof FinalizePackageLockedError) {
// HTTP route maps to 409 CONFLICT; retry after the in-progress run,
// or `rm <projectDir>/.finalize.lock` if it is stale.
return res.status(409).json({ code: 'CONFLICT', error: err.message });
}
throw err;
} Prevention
- Avoid triggering concurrent finalize on the same project (debounce the UI button).
- After a crash, clear stale `.finalize.lock` before retrying.
- Treat repeated 409s with no running finalize as a stale-lock condition.
When it happens
Trigger: Two concurrent finalize requests for the same project (e.g. double-clicking Finalize, two browser tabs), or a prior finalize that crashed (OOM, kill, daemon restart) leaving a stale `.finalize.lock` behind.
Common situations: Double-clicking the Finalize button; two tabs finalizing the same project; the daemon was killed mid-finalize; an OOM or panic aborted the process before the finally block unlinked the lock.
Related errors
- automation proposal not found
- cannot overwrite built-in automation template ${template.id}
- brand not found: ${id}
- brand.json not found in the extraction project — the agent h
- brand.json is not valid JSON.
AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12).
Data as JSON: /api/errors/00ee6131367a84ea.
Report an issue: GitHub.