{"record":{"id":"8bf0fe571a2764c7","repo":"Yeachan-Heo/oh-my-codex","slug":"worktreename-must-be-a-relative-safe-worktree-name","errorCode":null,"errorMessage":"worktreeName must be a relative safe worktree name","messagePattern":"worktreeName must be a relative safe worktree name","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/mcp/hermes-bridge.ts","lineNumber":479,"sourceCode":"    const message = error instanceof Error ? error.message : String(error);\n    if (message.startsWith(\"unsupported_session_kind\")) return failure(\"prompt_not_accepted\", message);\n    if (message.startsWith(\"job_not_input_accepting\")) return failure(\"prompt_not_accepted\", message);\n    if (message.includes(\"allow_mutation\")) return failure(\"mutation_not_allowed\", message);\n    return failure(\"invalid_input\", message);\n  }\n}\n\nexport async function hermesStartSession(\n  args: Record<string, unknown>,\n  deps: HermesBridgeDeps = {},\n): Promise<HermesBridgeResult<{ pid: number; command: string; args: string[]; workingDirectory: string }>> {\n  try {\n    requireMutation(args);\n    const cwd = resolveWorkingDirectoryForState(normalizeString(args.workingDirectory, \"workingDirectory\", { required: true }));\n    const prompt = normalizeString(args.prompt, \"prompt\", { required: true })!;\n    const worktreeName = normalizeString(args.worktreeName, \"worktreeName\");\n    if (worktreeName && (!/^[A-Za-z0-9._/-]{1,128}$/.test(worktreeName) || worktreeName.includes(\"..\") || worktreeName.startsWith(\"/\"))) {\n      throw new Error(\"worktreeName must be a relative safe worktree name\");\n    }\n    const command = (deps.resolveOmxCliEntryPath ?? resolveOmxCliEntryPath)({ cwd }) ?? \"omx\";\n    const launchArgs = [\"--tmux\", worktreeName ? `--worktree=${worktreeName}` : \"--worktree\", prompt];\n    const { TMUX: _tmux, TMUX_PANE: _tmuxPane, ...bridgeEnv } = process.env;\n    const child = (deps.spawnProcess ?? spawn)(command, launchArgs, {\n      cwd,\n      detached: true,\n      stdio: \"ignore\",\n      env: { ...bridgeEnv, OMX_HERMES_MCP_BRIDGE: \"1\" },\n    }) as ChildProcess;\n    child.unref();\n    if (!child.pid) return failure(\"command_failed\", \"OMX session launcher did not report a pid\");\n    return jsonResult({ pid: child.pid, command, args: launchArgs, workingDirectory: cwd });\n  } catch (error) {\n    const message = error instanceof Error ? error.message : String(error);\n    if (message.includes(\"allow_mutation\")) return failure(\"mutation_not_allowed\", message);\n    return failure(\"invalid_input\", message);\n  }","sourceCodeStart":461,"sourceCodeEnd":497,"githubUrl":"https://github.com/Yeachan-Heo/oh-my-codex/blob/3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2/src/mcp/hermes-bridge.ts#L461-L497","documentation":"When starting a Hermes session with a custom worktreeName, the name must be relative and safe: 1-128 chars of [A-Za-z0-9._/-], no '..' segments, and not starting with '/'. Violations throw this error before any process spawn, preventing path traversal or absolute-path worktree targets.","triggerScenarios":"Passing worktreeName like '../evil', '/abs/path', 'a..b', names with spaces or >128 chars to hermesStartSession.","commonSituations":"Deriving worktree names from user input or branch names without sanitization; branch names containing spaces, '..', or leading slash.","solutions":["Sanitize to [A-Za-z0-9._/-], strip leading '/' and collapse '..' segments","Keep the name under 128 characters","Slugify free-form input before passing it as worktreeName"],"exampleFix":"// before\n{ worktreeName: \"../shared/agent\" }\n// after\n{ worktreeName: \"shared-agent\" }","handlingStrategy":"validation","validationCode":"function safeWorktreeName(n: string): string | undefined {\n  const s = n.replace(/[^A-Za-z0-9._/-]/g,'-').replace(/\\.\\./g,'').replace(/^\\/+/, '').slice(0,128);\n  return s || undefined;\n}\nargs.worktreeName = safeWorktreeName(rawName);","typeGuard":"function isSafeWorktreeName(n: string): boolean { return /^[A-Za-z0-9._/-]{1,128}$/.test(n) && !n.includes('..') && !n.startsWith('/'); }","tryCatchPattern":"catch (e) { if ((e as Error).message === 'worktreeName must be a relative safe worktree name') { args.worktreeName = slugify(args.worktreeName); retry; } }","preventionTips":["Slugify branch/user input before using as worktree names","Reject '..' and leading '/' at the UI boundary","Cap names at 128 chars"],"tags":["mcp","hermes-bridge","worktree","path-safety","validation"],"backgroundTag":"unsafe-path-rejected","analyzedSha":"3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2","analyzedAt":"2026-08-27T22:18:39.783Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}