{"record":{"id":"2b61b9fce402f46c","repo":"can1357/oh-my-pi","slug":"daemon-name-must-be-1-48-letters-numbers-dots-u","errorCode":null,"errorMessage":"Daemon name must be 1-48 letters, numbers, dots, underscores, or hyphens","messagePattern":"Daemon name must be 1-48 letters, numbers, dots, underscores, or hyphens","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/launch/broker.ts","lineNumber":598,"sourceCode":"\t\t\t\tconst record = this.#record(operation.name);\n\t\t\t\tawait this.#stopRecord(record, operation.timeoutMs);\n\t\t\t\treturn { op: \"stop\", daemon: record.snapshot };\n\t\t\t}\n\t\t\tcase \"restart\":\n\t\t\t\treturn this.#restart(operation.name);\n\t\t\tcase \"describe\": {\n\t\t\t\tconst record = this.#record(operation.name);\n\t\t\t\tawait this.#refreshDetached(record);\n\t\t\t\treturn { op: \"describe\", daemon: record.snapshot, spec: record.spec };\n\t\t\t}\n\t\t\tcase \"shutdown\":\n\t\t\t\treturn { op: \"shutdown\" };\n\t\t}\n\t}\n\n\tasync #start(spec: DaemonSpec, owner?: string): Promise<DaemonRpcResult> {\n\t\tif (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,47}$/.test(spec.name)) {\n\t\t\tthrow new Error(\"Daemon name must be 1-48 letters, numbers, dots, underscores, or hyphens\");\n\t\t}\n\t\tif (spec.detached && spec.pty) {\n\t\t\tthrow new Error(\"A detached daemon cannot allocate a PTY\");\n\t\t}\n\t\tif (\n\t\t\tspec.pty &&\n\t\t\tprocess.platform === \"win32\" &&\n\t\t\t[\".bat\", \".cmd\"].includes(path.extname(spec.application).toLowerCase())\n\t\t) {\n\t\t\tthrow new Error('Windows batch files require application \"cmd.exe\" with the batch path after \"/c\"');\n\t\t}\n\t\tif (this.#startingNames.has(spec.name)) {\n\t\t\tthrow new Error(`Daemon ${spec.name} is already starting`);\n\t\t}\n\t\tthis.#startingNames.add(spec.name);\n\t\tlet record: ManagedDaemon;\n\t\ttry {\n\t\t\tconst existing = this.#records.get(spec.name);","sourceCodeStart":580,"sourceCodeEnd":616,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/launch/broker.ts#L580-L616","documentation":"DaemonBroker.#start validates the daemon name against /^[A-Za-z0-9][A-Za-z0-9._-]{0,47}$/ before creating anything: the name must be 1-48 chars, start with a letter or digit, and use only letters, digits, dots, underscores, hyphens. This guards downstream use of the name in process management, file paths, and subscription keys from path/lookup injection or collisions.","triggerScenarios":"Calling the broker's start/startDaemon RPC (via #start) with a name that is empty, longer than 48 chars, starts with '.', '_' or '-', or contains characters like spaces, slashes, '@', or unicode.","commonSituations":"Deriving daemon names from user input, project paths, or branch names that contain slashes or spaces; generating names via templating that yields empty strings; suffixing IDs to names and exceeding the 48-char limit; localized/unicode names from non-ASCII project titles.","solutions":["Rename the daemon to a 1-48 char string matching [A-Za-z0-9][A-Za-z0-9._-]* (must start with alphanumeric)","Sanitize derived names: strip/replace invalid characters and truncate to 48 chars, prefixing with an alphanumeric if needed","Validate the name client-side with the same regex before issuing the start request"],"exampleFix":"// before\nawait broker.start({ name: `daemon for ${projectPath}` }); // spaces and slashes -> throws\n// after\nconst safe = projectPath.replace(/[^A-Za-z0-9._-]+/g, '-').replace(/^[._-]+/, '').slice(0, 48) || 'daemon';\nawait broker.start({ name: safe });","handlingStrategy":"validation","validationCode":"const DAEMON_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,47}$/;\nfunction isValidDaemonName(name: string): boolean { return DAEMON_NAME_RE.test(name); }\nif (!isValidDaemonName(spec.name)) throw new Error(`Invalid daemon name: ${spec.name}`);","typeGuard":null,"tryCatchPattern":"try {\n  await broker.start(spec);\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith('Daemon name must be')) {\n    return broker.start({ ...spec, name: sanitizeDaemonName(spec.name) });\n  }\n  throw err;\n}","preventionTips":["Centralize a sanitizeDaemonName helper (replace [^A-Za-z0-9._-], strip leading dots/dashes, cap at 48 chars) and use it wherever names are derived","Never pass raw user input, paths, or branch names as daemon names","Add a form/UI-level validator mirroring the broker regex","Keep derived names short — appending suffixes can silently exceed the 48-char cap"],"tags":["validation","naming","daemon","input-validation"],"backgroundTag":"invalid-identifier-format","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}