{"record":{"id":"1c84d00c4b745774","repo":"can1357/oh-my-pi","slug":"maxagents-must-be-a-positive-integer","errorCode":null,"errorMessage":"maxAgents must be a positive integer","messagePattern":"maxAgents must be a positive integer","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/cleanse/loop.ts","lineNumber":77,"sourceCode":"/**\n * Stream diagnostics into a bounded worker pool, then verify the combined edits.\n *\n * Diagnostics are grouped per file and dispatched as they arrive: a new file\n * group goes to a fresh worker while fewer than `maxAgents` run; otherwise it\n * queues until a slot frees. Files stay sticky — two workers never edit the\n * same file concurrently. Late diagnostics for an owned file are steered into\n * the owning worker's chat via `followUp`; when that fails (worker not yet\n * registered or already finishing) they are requeued for a fresh worker once\n * the owner releases the file. Each diagnostic is dispatched at most once;\n * the final verification pass decides `clean`.\n */\nexport async function runCleanseLoop(\n\toptions: CleanseLoopOptions,\n\tdependencies: CleanseLoopDependencies,\n): Promise<CleanseLoopResult> {\n\tconst { maxAgents, signal } = options;\n\tif (!Number.isInteger(maxAgents) || maxAgents <= 0) {\n\t\tthrow new Error(\"maxAgents must be a positive integer\");\n\t}\n\n\tconst seen = new Set<string>();\n\t/** File key (`\"\"` = project-level) → queued diagnostics not yet assigned. */\n\tconst pending = new Map<string, CleanseDiagnostic[]>();\n\t/** File keys owned by an in-flight worker. */\n\tconst owned = new Map<string, OwnerEntry>();\n\tconst inFlight = new Map<number, { assignment: CleanseAssignment; done: Promise<void> }>();\n\tconst followUps = new Set<Promise<void>>();\n\tconst outcomes: CleanseAgentOutcome[] = [];\n\tlet dispatched = 0;\n\t/** Infrastructure failure from the dispatch seam (subagent errors settle as outcomes instead). */\n\tlet dispatchFailure: unknown;\n\n\t/** Queue one deduplicated diagnostic: held for its owner or pending for a fresh worker. */\n\tconst route = (diagnostic: CleanseDiagnostic, touched: Set<OwnerEntry>): void => {\n\t\tconst fileKey = diagnostic.file ?? \"\";\n\t\tconst entry = owned.get(fileKey);","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/cleanse/loop.ts#L59-L95","documentation":"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.","triggerScenarios":"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.","commonSituations":"Programmatic/SDK use of the cleanse loop where options are constructed by hand or deserialized from JSON that omitted maxAgents.","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."],"exampleFix":"// before\nawait runCleanseLoop({ signal, diagnostics }, deps); // maxAgents missing\n// after\nawait runCleanseLoop({ signal, diagnostics, maxAgents: 32 }, deps);","handlingStrategy":"validation","validationCode":"if (!Number.isInteger(options.maxAgents) || options.maxAgents <= 0) {\n  throw new Error(\"maxAgents must be a positive integer\");\n}\nawait runCleanseLoop(options, deps);","typeGuard":"function hasValidMaxAgents(o: { maxAgents?: number }): o is { maxAgents: number } & typeof o {\n  return Number.isInteger(o.maxAgents) && (o.maxAgents as number) > 0;\n}","tryCatchPattern":"try {\n  const result = await runCleanseLoop(loopOptions, deps);\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"maxAgents must be a positive integer\")) {\n    loopOptions.maxAgents = 32;\n    // retry with default or abort\n  } else throw err;\n}","preventionTips":["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."],"tags":["validation","api-misuse","argument-parsing"],"backgroundTag":"invalid-argument-value","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}