can1357/oh-my-pi · error · Error

invalid job name: ${jobName}

Error message

invalid job name: ${jobName}

What it means

Job names are used as path segments under the jobs directory, so the server rejects any name that is empty, '.', '..', or contains a path separator (/ or \). This is a path-traversal guard: it throws before a job name is ever used in a filesystem operation such as destroying a run (#destroyRun).

Source

Thrown at packages/metaharness/src/server.ts:96

	controller: ReadableStreamDefaultController<Uint8Array>;
	state: SseState;
}

function parseServerArgs(argv: string[]): { port: number; jobsDir: string } {
	let port = 4700;
	let jobsDir = DEFAULT_JOBS_DIR;
	for (let i = 0; i < argv.length; i++) {
		if (argv[i] === "--port" && argv[i + 1]) port = Number(argv[++i]);
		else if (argv[i] === "--jobs-dir" && argv[i + 1]) jobsDir = path.resolve(argv[++i]);
	}
	if (!Number.isSafeInteger(port) || port < 1 || port > 65535) throw new Error("--port must be 1..65535");
	return { port, jobsDir };
}

/** Job names are single path segments; anything else could escape the jobs dir. */
function assertSafeJobName(jobName: string): void {
	if (!jobName || jobName === "." || jobName === ".." || /[/\\]/.test(jobName)) {
		throw new Error(`invalid job name: ${jobName}`);
	}
}

/** True when `pid` names a live process (signal-0 probe). */
function pidAlive(pid: number | null): boolean {
	if (pid == null) return false;
	try {
		process.kill(pid, 0);
		return true;
	} catch {
		return false;
	}
}

/**
 * Resolve the launch request for a new arm added to an existing experiment.
 * Inherits the experiment's benchmark, dataset, and — crucially — the exact
 * task sample from a sibling arm (its recorded `include`, else its observed

View on GitHub (pinned to 9690622007)

Solutions

  1. Send only the bare job name (single path segment), e.g. 'my-run-2026-08-31', not a path
  2. Trim/validate the job name on the client before calling the destroy endpoint
  3. URL-encode the job name correctly in requests; inspect the actual value received
  4. Fix the caller that derives job names from file paths to strip directory components

Example fix

// before (client)
await fetch(`/destroy?job=${encodeURIComponent(fullPath)}`); // 'runs/_bench/job1' -> rejected

// after (client)
const jobName = path.basename(fullPath);
await fetch(`/destroy?job=${encodeURIComponent(jobName)}`); // 'job1'
Defensive patterns

Strategy: validation

Validate before calling

function assertSafeJobName(jobName) {
  if (!jobName || jobName === "." || jobName === ".." || /[/\\]/.test(jobName)) {
    throw new Error(`invalid job name: ${jobName}`);
  }
}
// call client-side before requesting destroy: assertSafeJobName(jobName)

Type guard

function isSafeJobName(v) {
  return typeof v === "string" && v.length > 0 && v !== "." && v !== ".." && !/[/\\]/.test(v);
}

Try / catch

try {
  await destroyRun(jobName);
} catch (err) {
  if (err instanceof Error && err.message.startsWith("invalid job name:")) {
    console.error(`Refusing to destroy: '${jobName}' is not a single path segment`);
    return;
  }
  throw err;
}

Prevention

When it happens

Trigger: An HTTP request to the destroy-run endpoint (or any caller of #destroyRun) supplies a jobName that is empty, '.', '..', or contains a slash/backslash, causing assertSafeJobName to throw.

Common situations: Client-side bug embedding a full path like 'jobs/my-run' instead of just 'my-run'; URL-encoded slashes arriving in a request param; empty job id from a stale UI reference; deliberate path-traversal attempt.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/50131f1f2739f67c. Report an issue: GitHub.