can1357/oh-my-pi · error · Error
arm must be a non-empty [A-Za-z0-9_.-] token
Error message
arm must be a non-empty [A-Za-z0-9_.-] token
What it means
resolveArmLaunch validates the requested arm name before building a LaunchRequest. An arm name is used verbatim in the job name (`${experimentId}-${arm}`), which becomes a filesystem directory, so it must be a non-empty token limited to [A-Za-z0-9_.-]. This error means the `arm` field of the AddArmRequest was missing, empty, or contained forbidden characters.
Source
Thrown at packages/metaharness/src/server.ts:120
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
* trial tasks) so the arm is directly comparable. Only per-arm knobs (model,
* prewalk, role, note, extra args) come from `req`. Throws if the experiment has
* no runs to inherit from or the arm name is taken.
*/
export function resolveArmLaunch(store: RunStore, experimentId: string, req: AddArmRequest): LaunchRequest {
if (!req.arm || /[^\w.-]/.test(req.arm)) throw new Error("arm must be a non-empty [A-Za-z0-9_.-] token");
if (!req.model) throw new Error("model is required");
const siblings = store.listRuns().filter(r => experimentOf(r.jobName) === experimentId);
if (siblings.length === 0) throw new Error(`experiment '${experimentId}' has no runs to inherit from`);
// Template = the sibling whose recorded `include` list is the longest (the
// fullest expression of the experiment's sample — partial re-run arms
// record subsets); among include-less siblings, the most observed trials.
// listRuns is newest-first so ties keep the newest.
const strings = (v: unknown): string[] =>
Array.isArray(v) ? v.filter((x): x is string => typeof x === "string") : [];
const recordedInclude = (r: RunRow): string[] => strings((r.config as Partial<LaunchRequest>).include);
const score = (r: RunRow): [number, number] => {
const recorded = recordedInclude(r).length;
return recorded > 0 ? [1, recorded] : [0, store.listTraces(r.jobName).length];
};
let template = siblings[0];
let templateScore = score(template);
for (const r of siblings.slice(1)) {
const s = score(r);View on GitHub (pinned to 9690622007)
Solutions
- Set req.arm to a non-empty string containing only letters, digits, underscore, dot, or hyphen (e.g. 'baseline-v2').
- If deriving the arm from a model id, slugify it first: arm.replace(/[^\w.-]/g, '-').
- Validate client-side before calling: if (!arm || /[^\w.-]/.test(arm)) throw.
- Check that the arm field name is spelled correctly and not shadowed by undefined in the request object.
Example fix
// before
await server.addArm(expId, { arm: `${model}` , model }); // model='openai/gpt-4o'
// after
const arm = model.replace(/[^\w.-]/g, "-"); // 'openai-gpt-4o'
await server.addArm(expId, { arm, model }); Defensive patterns
Strategy: validation
Validate before calling
function validArm(arm: unknown): arm is string {
return typeof arm === "string" && arm.length > 0 && !/[^\w.-]/.test(arm);
}
if (!validArm(req.arm)) throw new Error("arm must match [A-Za-z0-9_.-]"); Type guard
const isSafeToken = (v: unknown): v is string => typeof v === "string" && /^[A-Za-z0-9_.-]+$/.test(v);
Try / catch
try {
server.addArm(expId, req);
} catch (err) {
if (err instanceof Error && err.message.startsWith("arm must be")) {
req.arm = req.arm?.replace(/[^\w.-]/g, "-") || "arm-1";
server.addArm(expId, req);
} else throw err;
} Prevention
- Slugify any external id (model ids, labels) before using it as an arm name.
- Keep a shared regex constant for arm names and validate at the UI/CLI boundary.
- Never embed raw model ids containing '/' in arm names.
- Unit-test arm-name sanitization with the exact rejection regex.
When it happens
Trigger: Calling resolveArmLaunch (directly or via addArm) with req.arm undefined, empty string '', or a name containing whitespace, '/', ':', or other non-[\w.-] characters — e.g. { arm: 'gpt/5', model: 'x' } or { model: 'x' } with no arm at all.
Common situations: Interpolating a model id with slashes (e.g. 'openai/gpt-4o') into the arm name; forgetting to set the arm field when constructing the request programmatically; pasting a label with spaces or colons from a dashboard.
Related errors
- Invalid skill name "${raw}". Use lowercase letters, digits,
- Unknown tool${unknown.length === 1 ? "" : "s"} in --tools: $
- Unknown OAuth provider '${providerArg}'. Known: ${providers
- Invalid marketplace plugin package name: ${JSON.stringify(na
- Invalid plugin name: "${name}"
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/c928c2dd829a635c.
Report an issue: GitHub.