{"record":{"id":"674b9029c2ed9ca3","repo":"moeru-ai/airi","slug":"skip-cannot-be-mixed-with-other-tool-calls-in-th","errorCode":null,"errorMessage":"skip() cannot be mixed with other tool calls in the same script","messagePattern":"skip\\(\\) cannot be mixed with other tool calls in the same script","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"integrations/minecraft/src/cognitive/conscious/js-planner.ts","lineNumber":1405,"sourceCode":"        return { [keys[0]]: firstArg }\n    }\n\n    const params: Record<string, unknown> = {}\n    for (const [index, key] of keys.entries()) {\n      if (index >= args.length)\n        break\n      params[key] = args[index]\n    }\n\n    return params\n  }\n\n  private async runAction(tool: string, params: Record<string, unknown>): Promise<ActionRuntimeResult> {\n    if (!this.activeRun)\n      throw new Error('Tool calls are only allowed during REPL evaluation')\n\n    if (this.activeRun.sawSkip && tool !== 'skip')\n      throw new Error('skip() cannot be mixed with other tool calls in the same script')\n\n    if (this.activeRun.actionCount >= this.maxActionsPerTurn)\n      throw new Error(`Action limit exceeded: max ${this.maxActionsPerTurn} actions per turn`)\n\n    if (tool === 'skip')\n      this.activeRun.sawSkip = true\n\n    this.activeRun.actionCount++\n\n    if (tool === 'skip') {\n      const action: ActionInstruction = { tool: 'skip', params: {} }\n      const runtimeResult: ActionRuntimeResult = {\n        action,\n        ok: true,\n        result: 'Skipped turn',\n      }\n      this.activeRun.executed.push(runtimeResult)\n      return runtimeResult","sourceCodeStart":1387,"sourceCodeEnd":1423,"githubUrl":"https://github.com/moeru-ai/airi/blob/27111382b4a79a7e983289d6e983a06af185ed0f/integrations/minecraft/src/cognitive/conscious/js-planner.ts#L1387-L1423","documentation":"Thrown by JavaScriptPlanner.runAction() when this.activeRun.sawSkip is already true and a subsequent non-skip tool is invoked. skip() is a terminal 'do nothing this turn' sentinel; mixing it with real actions is contradictory, so the planner rejects it. sawSkip is set the moment skip runs, so any later tool call (in the same script, even async) trips this guard.","triggerScenarios":"A script calls skip() and then later calls use('moveTo', ...) or any non-skip tool; an async flow where skip() resolves first and a pending action call follows; the model uses skip as a 'default' then overrides it with a real action.","commonSituations":"Model writes conditional logic with skip in one branch and a real action in another, both reachable; skip used as a fallback after an early return that did not actually return; retry logic that calls skip then re-attempts a real tool.","solutions":["Treat skip() as strictly terminal: never call another tool after it in the same script.","Branch with if/else so only one of skip or the real action runs.","If you want to do nothing, call skip() alone and return."],"exampleFix":"// before\n//   if (tired) { skip() }\n//   await use('mineBlockAt', { position: p })  // throws if skip ran\n//\n// after\n//   if (tired) { skip(); return }\n//   await use('mineBlockAt', { position: p })","handlingStrategy":"validation","validationCode":"// Treat skip() as terminal and never call another tool afterwards:\n// if (shouldSkip) { skip(); return }\n// await use('moveTo', target)  // only reached when not skipping","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never call a non-skip tool after skip() in the same script.","Use if/else branching so only one of skip or the action is reachable.","Return immediately after skip() to make the terminal intent explicit.","Avoid retry loops that call skip() and then re-attempt a real action."],"tags":["llm-planner","runtime","skip","action-ordering"],"backgroundTag":null,"analyzedSha":"27111382b4a79a7e983289d6e983a06af185ed0f","analyzedAt":"2026-08-12T18:33:34.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}