can1357/oh-my-pi · error · Error
maxAgents must be a positive integer
Error message
maxAgents must be a positive integer
What it means
runCleanseLoop performs the same internal validation as runCleanse: CleanseLoopOptions.maxAgents must be a positive integer. This is the loop-level guard so direct callers of the loop API cannot bypass the CLI-level check.
Source
Thrown at packages/coding-agent/src/cleanse/loop.ts:77
/**
* Stream diagnostics into a bounded worker pool, then verify the combined edits.
*
* Diagnostics are grouped per file and dispatched as they arrive: a new file
* group goes to a fresh worker while fewer than `maxAgents` run; otherwise it
* queues until a slot frees. Files stay sticky — two workers never edit the
* same file concurrently. Late diagnostics for an owned file are steered into
* the owning worker's chat via `followUp`; when that fails (worker not yet
* registered or already finishing) they are requeued for a fresh worker once
* the owner releases the file. Each diagnostic is dispatched at most once;
* the final verification pass decides `clean`.
*/
export async function runCleanseLoop(
options: CleanseLoopOptions,
dependencies: CleanseLoopDependencies,
): Promise<CleanseLoopResult> {
const { maxAgents, signal } = options;
if (!Number.isInteger(maxAgents) || maxAgents <= 0) {
throw new Error("maxAgents must be a positive integer");
}
const seen = new Set<string>();
/** File key (`""` = project-level) → queued diagnostics not yet assigned. */
const pending = new Map<string, CleanseDiagnostic[]>();
/** File keys owned by an in-flight worker. */
const owned = new Map<string, OwnerEntry>();
const inFlight = new Map<number, { assignment: CleanseAssignment; done: Promise<void> }>();
const followUps = new Set<Promise<void>>();
const outcomes: CleanseAgentOutcome[] = [];
let dispatched = 0;
/** Infrastructure failure from the dispatch seam (subagent errors settle as outcomes instead). */
let dispatchFailure: unknown;
/** Queue one deduplicated diagnostic: held for its owner or pending for a fresh worker. */
const route = (diagnostic: CleanseDiagnostic, touched: Set<OwnerEntry>): void => {
const fileKey = diagnostic.file ?? "";
const entry = owned.get(fileKey);View on GitHub (pinned to 9690622007)
Solutions
- Provide options.maxAgents as a positive integer before calling runCleanseLoop.
- Mirror the CLI validation in your wrapper before constructing CleanseLoopOptions.
- Use the same default as runCleanse (32) when the caller did not specify a value.
Example fix
// before
await runCleanseLoop({ signal, diagnostics }, deps); // maxAgents missing
// after
await runCleanseLoop({ signal, diagnostics, maxAgents: 32 }, deps); Defensive patterns
Strategy: validation
Validate before calling
if (!Number.isInteger(options.maxAgents) || options.maxAgents <= 0) {
throw new Error("maxAgents must be a positive integer");
}
await runCleanseLoop(options, deps); Type guard
function hasValidMaxAgents(o: { maxAgents?: number }): o is { maxAgents: number } & typeof o {
return Number.isInteger(o.maxAgents) && (o.maxAgents as number) > 0;
} Try / catch
try {
const result = await runCleanseLoop(loopOptions, deps);
} catch (err) {
if (err instanceof Error && err.message.includes("maxAgents must be a positive integer")) {
loopOptions.maxAgents = 32;
// retry with default or abort
} else throw err;
} Prevention
- Centralize option construction in one factory that applies the default (32).
- Validate deserialized JSON options before passing them to the loop API.
- Add a type-level branded PositiveInt for maxAgents in your wrapper.
When it happens
Trigger: Invoking runCleanseLoop with options.maxAgents undefined, 0, negative, non-integer, or NaN — typically when calling the loop API directly (tests/programmatic use) instead of through runCleanse.
Common situations: Programmatic/SDK use of the cleanse loop where options are constructed by hand or deserialized from JSON that omitted maxAgents.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- invalid {} argument: {}
- invalid Zero increment value: {}
- Gemini Files API delete requires a valid file name
- --agents must be a positive integer
- --trusted-extension requires a non-empty, non-flag value
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/1c84d00c4b745774.
Report an issue: GitHub.