{"record":{"id":"133631ab29c3dae8","repo":"can1357/oh-my-pi","slug":"experiment-id-must-be-a-non-empty-token-of-a-za-z","errorCode":null,"errorMessage":"experiment id must be a non-empty token of [A-Za-z0-9_.] (runs group as `<id>-<arm>`)","messagePattern":"experiment id must be a non-empty token of \\[A-Za-z0-9_\\.\\] \\(runs group as `<id>-<arm>`\\)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/metaharness/src/server.ts","lineNumber":527,"sourceCode":"\t\t\tthis.#tick();\n\t\t});\n\t\tthis.#store.registerLaunch({ ...record, pid: proc.pid });\n\t\tthis.#tick();\n\t\treturn proc.pid;\n\t}\n\n\t/** Liveness check that survives manager restarts: managed child, or a running row with a live pid. */\n\t#runLive(run: RunRow): boolean {\n\t\treturn this.#children.has(run.jobName) || (run.status === \"running\" && pidAlive(run.pid));\n\t}\n\n\t/** Register an experiment id (with an optional goal) so it is browsable before its first arm. */\n\tcreateExperiment(req: CreateExperimentRequest): { id: string; goal: string } {\n\t\tconst id = req.id?.trim() ?? \"\";\n\t\t// Dashes are structurally impossible: `experimentOf` groups job names by\n\t\t// the token before the first dash, so a dashed id could never own a run.\n\t\tif (!/^[A-Za-z0-9_.]+$/.test(id)) {\n\t\t\tthrow new Error(\"experiment id must be a non-empty token of [A-Za-z0-9_.] (runs group as `<id>-<arm>`)\");\n\t\t}\n\t\tconst goal = req.goal ?? this.#store.getExperimentMeta(id)?.goal ?? \"\";\n\t\tthis.#store.setExperimentGoal(id, goal);\n\t\treturn { id, goal };\n\t}\n\n\t/** Apply goal + per-run role/note metadata; used by the UI and for backfill. */\n\tupdateExperimentMeta(id: string, update: ExperimentMetaUpdate): { id: string; updatedRuns: string[] } {\n\t\tif (update.goal !== undefined) this.#store.setExperimentGoal(id, update.goal);\n\t\tconst updatedRuns: string[] = [];\n\t\tfor (const jobName in update.runs) {\n\t\t\tif (experimentOf(jobName) !== id) continue;\n\t\t\tif (this.#store.setRunMeta(jobName, update.runs[jobName])) updatedRuns.push(jobName);\n\t\t}\n\t\tthis.#tick();\n\t\treturn { id, updatedRuns };\n\t}\n","sourceCodeStart":509,"sourceCodeEnd":545,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/metaharness/src/server.ts#L509-L545","documentation":"createExperiment validates the experiment id against `/^[A-Za-z0-9_.]+$/` and throws this error for anything else (empty, whitespace-only, or containing dashes/spaces/slashes). Dashes are structurally forbidden because `experimentOf` groups job names by the token before the first dash — a dashed id could never own its runs. The id must be a non-empty token of letters, digits, underscore, and dot.","triggerScenarios":"Calling `createExperiment({ id })` with an empty/undefined id, an id containing `-`, spaces, `/`, or other punctuation, or an id with leading/trailing whitespace that trims to empty.","commonSituations":"Deriving the experiment id from a branch name or URL slug that contains dashes; passing a raw user string without sanitizing; forgetting to pass `id` at all.","solutions":["Sanitize the id: replace disallowed characters with `_` or `.` before calling createExperiment","Remove dashes (e.g. `my-exp` → `my_exp` or `myexp`) since dashes are the run-name grouping separator","Ensure the id is non-empty after trimming","Validate with the same regex client-side: /^[A-Za-z0-9_.]+$/"],"exampleFix":"// before\nserver.createExperiment({ id: \"feat/new-agent\" }); // throws\n// after\nconst raw = \"feat/new-agent\";\nconst id = raw.replace(/[^A-Za-z0-9_.]/g, \"_\"); // \"feat_new_agent\"\nserver.createExperiment({ id });","handlingStrategy":"validation","validationCode":"const EXPERIMENT_ID_RE = /^[A-Za-z0-9_.]+$/;\nfunction validExperimentId(id: string | undefined): boolean {\n  return typeof id === \"string\" && EXPERIMENT_ID_RE.test(id.trim());\n}","typeGuard":"function isValidExperimentId(id: unknown): id is string {\n  return typeof id === \"string\" && /^[A-Za-z0-9_.]+$/.test(id);\n}","tryCatchPattern":"try {\n  server.createExperiment({ id });\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith(\"experiment id must be\")) {\n    throw new Error(`invalid experiment id \"${id}\": use [A-Za-z0-9_.], no dashes`);\n  }\n  throw err;\n}","preventionTips":["Sanitize derived ids (branch names, slugs) with .replace(/[^A-Za-z0-9_.]/g, \"_\")","Never use dashes — they are the arm-grouping separator","Trim ids before sending","Validate client-side with the same regex"],"tags":["validation","naming"],"backgroundTag":"invalid-identifier","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}