{"record":{"id":"f80f1fb36c00df61","repo":"nexu-io/open-design","slug":"project-is-required-string","errorCode":null,"errorMessage":"project is required (string).","messagePattern":"project is required \\(string\\)\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/daemon/src/mcp.ts","lineNumber":2964,"sourceCode":"    throw new Error(\n      `project arg omitted and active context lookup failed: ${errorMessage(err)}. Pass project=\"<id-or-name>\".`,\n    );\n  }\n  if (!active || active.active === false || !active.projectId) {\n    throw new Error(\n      'project arg omitted and Open Design has no active project. The active context expires about 5 minutes after the last user interaction with Open Design - the user may need to click into a project to wake it up. Otherwise pass project=\"<id-or-name>\".',\n    );\n  }\n  return { id: active.projectId, resolved: null, active };\n}\n\nasync function resolveProjectId(\n  baseUrl: string,\n  arg: unknown,\n  headers?: Record<string, string>,\n): Promise<ResolvedProject> {\n  if (typeof arg !== 'string' || !arg) {\n    throw new Error('project is required (string).');\n  }\n  if (UUID_RE.test(arg)) return { id: arg, name: arg, source: 'uuid' as const };\n\n  const list = await fetchProjectList(baseUrl, headers);\n  if (list.length === 0) {\n    throw new Error('no projects on this daemon');\n  }\n\n  const lower = arg.toLowerCase();\n  const norm = (s: unknown): string =>\n    String(s || '')\n      .toLowerCase()\n      .replace(/\\s*\\(\\d+\\)\\s*$/, '')\n      .replace(/[\\s_-]+/g, '-');\n  const target = norm(arg);\n\n  const idMatch = list.find((p) => p.id === arg);\n  if (idMatch) return { id: idMatch.id, name: idMatch.name, source: 'id' as const };","sourceCodeStart":2946,"sourceCodeEnd":2982,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/mcp.ts#L2946-L2982","documentation":"Thrown by resolveProjectId, the lower-level resolver reached only after the caller explicitly supplied a `project` arg. It requires a non-empty string. Because the higher-level resolveProjectArg only forwards truthy strings to resolveProjectId, hitting this directly usually means an internal caller invoked resolveProjectId with undefined/empty, or a code path bypassed the arg guard.","triggerScenarios":"An internal daemon/MCP code path calls resolveProjectId directly with an undefined/empty value; a typed boundary let a non-string slip through; refactoring removed the upstream truthy-string check.","commonSituations":"Code refactor that introduces a new call site of resolveProjectId without pre-validation; passing a value typed as `any` that is actually undefined.","solutions":["If you are an end user, pass a non-empty `project` string (name or UUID).","If you are an internal caller, validate/guard the value before invoking resolveProjectId, or route through resolveProjectArg instead.","Treat this error as a bug if it surfaces from a documented MCP tool — file an issue with the call args."],"exampleFix":"// before\nresolveProjectId(baseUrl, maybeEmpty, headers)\n// after\nif (typeof arg !== 'string' || !arg) throw new Error('project is required (string).');\nresolveProjectId(baseUrl, arg, headers)","handlingStrategy":"validation","validationCode":"if (typeof project !== 'string' || project.trim() === '') {\n  throw new Error('project is required (string).');\n}","typeGuard":"function isNonEmptyString(v: unknown): v is string {\n  return typeof v === 'string' && v.length > 0;\n}","tryCatchPattern":null,"preventionTips":["Pre-validate the project arg at your boundary before calling daemon helpers.","Prefer resolveProjectArg over resolveProjectId in app code."],"tags":["mcp","project-resolution","invariant","validation"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}