{"record":{"id":"be66c09b28e69da2","repo":"vercel/ai","slug":"invalid-pi-label-name-name","errorCode":null,"errorMessage":"Invalid Pi ${label} name: ${name}","messagePattern":"Invalid Pi (.+?) name: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/harness-pi/src/pi-utils.ts","lineNumber":78,"sourceCode":"    return output;\n  }\n  const serialized = JSON.stringify(output);\n  return serialized ?? 'null';\n}\n\nexport function getErrorText(error: unknown): string {\n  return error instanceof Error ? error.message : String(error);\n}\n\n/**\n * Validate that a name is safe to use as a filesystem path segment under\n * `.pi/skills/<name>/` or `.pi/agents/<name>.md`. Refuses anything that\n * could be interpreted as a path traversal or contains shell-sensitive\n * characters.\n */\nexport function safePiMetadataSegment(name: string, label: string): string {\n  if (!/^[A-Za-z0-9._-]+$/.test(name) || name === '.' || name === '..') {\n    throw new Error(`Invalid Pi ${label} name: ${name}`);\n  }\n  return name;\n}\n\n/** Frontmatter renderer for `.pi/skills/<name>/SKILL.md`. */\nexport function renderPiSkillFile(skill: HarnessV1Skill): string {\n  return `---\\nname: ${skill.name}\\ndescription: ${skill.description}\\n---\\n\\n${skill.content}`;\n}\n","sourceCodeStart":60,"sourceCodeEnd":87,"githubUrl":"https://github.com/vercel/ai/blob/69428b1f8b037e4d118fb4853428d5c4e620493c/packages/harness-pi/src/pi-utils.ts#L60-L87","documentation":"safePiMetadataSegment validates names used in Pi metadata file paths such as `.pi/skills/<name>/` or `.pi/agents/<name>.md`. It only allows `[A-Za-z0-9._-]` and rejects '.' and '..' to prevent path traversal and shell-sensitive characters. Any name containing slashes, spaces, or other special characters causes this throw.","triggerScenarios":"Calling safePiMetadataSegment(name, label) — directly or via skill/agent rendering functions like renderPiSkillFile — with a name containing '/', '\\', spaces, unicode, or equal to '.' or '..'.","commonSituations":"Deriving a skill name from a URL slug or file path (e.g. 'my/skill'); user-entered skill names with spaces ('my skill'); auto-generated names with colons or slashes from tool IDs; localized names with non-ASCII characters.","solutions":["Sanitize the name before passing it: lowercase, replace invalid characters with '-', trim.","Derive the name from the last path segment and strip extensions/slashes.","Catch the error and surface a clear validation message to the user configuring the skill/agent.","If you control upstream naming, restrict IDs to the allowed charset at creation time."],"exampleFix":"// before\nrenderPiSkillFile({ name: 'code-review/assistant', ... });\n// after\nconst safeName = 'code-review/assistant'\n  .split('/')\n  .pop()!\n  .replace(/[^A-Za-z0-9._-]+/g, '-')\n  .replace(/^\\.+$/, '');\nrenderPiSkillFile({ name: safeName, ... });","handlingStrategy":"validation","validationCode":"const PI_NAME_RE = /^[A-Za-z0-9._-]+$/;\nfunction validatePiName(name, label) {\n  if (!PI_NAME_RE.test(name) || name === '.' || name === '..') {\n    throw new Error(`Invalid Pi ${label} name: ${name}`);\n  }\n}\nvalidatePiName(skill.name, 'skill');","typeGuard":null,"tryCatchPattern":"try {\n  renderPiSkillFile(skill);\n} catch (err) {\n  if (String(err.message).startsWith('Invalid Pi ')) {\n    skill.name = slugify(skill.name);\n    return renderPiSkillFile(skill);\n  }\n  throw err;\n}","preventionTips":["Restrict skill/agent IDs to [A-Za-z0-9._-] at creation time","Slugify names derived from user input, URLs, or file paths","Never build names by joining path segments"],"tags":["validation","path-traversal","naming","security"],"backgroundTag":"invalid-name-error","analyzedSha":"69428b1f8b037e4d118fb4853428d5c4e620493c","analyzedAt":"2026-08-30T12:32:21.016Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}