{"record":{"id":"b65bd5de2b81191e","repo":"paperclipai/paperclip","slug":"cannot-build-api-path-with-an-empty-path-segment","errorCode":null,"errorMessage":"Cannot build API path with an empty path segment.","messagePattern":"Cannot build API path with an empty path segment\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"cli/src/commands/client/common.ts","lineNumber":128,"sourceCode":"\nexport function resolveApiBase(options: Pick<BaseClientOptions, \"apiBase\" | \"config\">, profile: ClientContextProfile = {}): string {\n  return normalizeApiBase(\n    options.apiBase?.trim() ||\n    process.env.PAPERCLIP_API_URL?.trim() ||\n    profile.apiBase ||\n    inferApiBaseFromConfig(options.config),\n  );\n}\n\nexport function normalizeApiBase(apiBase: string): string {\n  return apiBase.trim().replace(/\\/+$/, \"\");\n}\n\nexport function apiPath(strings: TemplateStringsArray, ...values: Array<string | number | boolean | null | undefined>): string {\n  let path = strings[0] ?? \"\";\n  values.forEach((value, index) => {\n    if (value === null || value === undefined || String(value).trim() === \"\") {\n      throw new Error(\"Cannot build API path with an empty path segment.\");\n    }\n    path += `${encodeURIComponent(String(value))}${strings[index + 1] ?? \"\"}`;\n  });\n  return path;\n}\n\nexport function inferContentTypeFromPath(filePath: string): string | undefined {\n  const ext = filePath.split(/[\\\\/]/).pop()?.split(\".\").pop()?.toLowerCase();\n  if (!ext) return undefined;\n  // These MIME strings are matched against the server's issue-attachment\n  // allowlist (server/src/attachment-types.ts DEFAULT_ALLOWED_TYPES) by EXACT\n  // string, so text types must carry no \"; charset=...\" parameter or the upload\n  // is rejected with \"422 Unsupported attachment content type\". Keep this set in\n  // sync with that allowlist (plus svg/avif, accepted by the asset routes).\n  return {\n    avif: \"image/avif\",\n    csv: \"text/csv\",\n    gif: \"image/gif\",","sourceCodeStart":110,"sourceCodeEnd":146,"githubUrl":"https://github.com/paperclipai/paperclip/blob/67001ec6eb96ae601aa27bc91d9b2415d665334a/cli/src/commands/client/common.ts#L110-L146","documentation":"Thrown by the apiPath tagged template in common.ts when any interpolated segment is null, undefined, or trims to an empty string. apiPath is the path builder used across CLI commands (e.g. apiPath`/api/agents/${agentRef}`), and it URL-encodes each segment; an empty segment would produce a doubled slash / ambiguous route, so it fails fast instead.","triggerScenarios":"Passing an undefined variable into an apiPath interpolation: e.g. apiPath`/api/agents/${agentId}` when agentId is undefined, apiPath`/api/companies/${companyId}/...` when companyId is empty, or a numeric 0/empty boolean. Any caller that did not validate its id before formatting the path.","commonSituations":"A command path computed ids from user input/env and skipped validation. A slug resolver returned undefined. A company-scoped call where companyId was never resolved (closely related to [17], but this fires later, at path-build time).","solutions":["Validate the id is a non-empty string before calling the API: `if (!id) throw new Error('id required')`.","For company-scoped calls, ensure --company-id / PAPERCLIP_COMPANY_ID / profile companyId is set (see [17]).","Guard upstream: resolveCommandContext with requireCompany:true catches missing company earlier.","If the id is legitimately optional, branch the path construction rather than interpolating undefined."],"exampleFix":"// before\nawait api.get(apiPath`/api/agents/${agentId}`);  // agentId undefined\n// after\nif (!agentId) throw new Error(\"agentId is required\");\nawait api.get(apiPath`/api/agents/${agentId}`);","handlingStrategy":"validation","validationCode":"function buildPath(segments: Array<string | number | undefined>): string {\n  const parts = segments.map((s) => {\n    if (s === null || s === undefined || String(s).trim() === '') {\n      throw new Error(`Empty path segment in ${JSON.stringify(segments)}`);\n    }\n    return encodeURIComponent(String(s));\n  });\n  return parts.join('/');\n}","typeGuard":"function isNonEmptyId(v: unknown): v is string {\n  return typeof v === 'string' && v.trim().length > 0;\n}","tryCatchPattern":"try { apiPath`/api/agents/${agentId}`; }\ncatch (err) {\n  const msg = err instanceof Error ? err.message : '';\n  if (msg === 'Cannot build API path with an empty path segment.') {\n    console.error('Tried to build a path with a missing id. Validate inputs before formatting.');\n    process.exit(2);\n  }\n  throw err;\n}","preventionTips":["Validate every id is a non-empty string BEFORE interpolating into apiPath.","For company-scoped paths, ensure companyId is resolved first (see [17]).","Prefer resolveCommandContext({ requireCompany: true }) to fail earlier and more clearly.","Never pass optional fields directly into the template; branch when they are absent."],"tags":["cli","validation","api-path","fail-fast","usage"],"backgroundTag":null,"analyzedSha":"67001ec6eb96ae601aa27bc91d9b2415d665334a","analyzedAt":"2026-08-12T12:05:45.408Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}