nexu-io/open-design · error · Error

project not found: ${projectId}

Error message

project not found: ${projectId}

What it means

getProject(db, projectId) returned null inside finalizeDesignPackage. This is defensive: the route handler validates the project exists and returns 404 before reaching this function. It is retained for direct (non-HTTP) callers such as CLI scripts. When reached, it surfaces as a generic Error which the route maps to 500 INTERNAL_ERROR.

Source

Thrown at apps/daemon/src/design/finalize-design.ts:274

      manifest: newest.artifactManifest ?? null,
    };
  }

  return null;
}

export async function finalizeDesignPackage(
  db: Db,
  projectsRoot: string,
  designSystemsRoot: string,
  projectId: string,
  options: FinalizeOptions,
): Promise<FinalizeAnthropicResponse> {
  const project = getProject(db, projectId);
  if (!project) {
    // Defensive — the route handler validates this and returns 404 before
    // reaching here. Kept for direct (non-HTTP) callers, e.g. CLI scripts.
    throw new Error(`project not found: ${projectId}`);
  }

  // Imported-folder projects (created via /api/import/folder) carry
  // `metadata.baseDir` and write to the user's actual folder rather than
  // `.od/projects/<id>`. resolveProjectDir handles both shapes; calling
  // bare `projectDir` would silently land DESIGN.md in the hidden daemon
  // data dir for these projects (PR #832 P1 finding from @lefarcen).
  const projectMetadata = (project as { metadata?: { baseDir?: string } | null }).metadata ?? null;
  const dir = resolveProjectDir(projectsRoot, projectId, projectMetadata ?? undefined);
  // For imported-folder projects, `dir` is the user's own directory and
  // already exists; mkdirSync is a no-op (recursive:true is idempotent).
  // 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')}`,

View on GitHub (pinned to 5be4028344)

Solutions

  1. Verify the project exists with getProject(db, projectId) before calling finalizeDesignPackage.
  2. Prefer the HTTP endpoint POST /api/projects/:id/finalize, which validates existence and returns 404 cleanly.
  3. Re-check the projectId spelling / source if it came from a stale cache.
Defensive patterns

Strategy: validation

Validate before calling

// Confirm the project exists before calling finalizeDesignPackage directly.
import { getProject } from '../db.js';
if (!getProject(db, projectId)) {
  throw new Error(`project not found: ${projectId}`);
}
const result = await finalizeDesignPackage(db, projectsRoot, dsRoot, projectId, options);

Type guard

function projectExists(db: Db, id: string): boolean {
  return getProject(db, id) !== null;
}

Try / catch

try {
  await finalizeDesignPackage(db, projectsRoot, dsRoot, projectId, options);
} catch (err) {
  if (String(err).startsWith('project not found:')) {
    // direct (non-HTTP) caller: surface a clean 404-equivalent
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling finalizeDesignPackage directly (not via the HTTP route) with a projectId that has no row in the projects table; or a race where the project was deleted between the route's existence check and the call.

Common situations: A CLI/automation script referencing a deleted or mistyped project ID; a stale project ID cached client-side; the project was removed in another tab between the 404 pre-check and the call (rare).

Related errors


AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12). Data as JSON: /api/errors/cf8303e71de74c91. Report an issue: GitHub.