{"record":{"id":"fc8a88e515313e25","repo":"moeru-ai/airi","slug":"your-last-reply-was-natural-language-not-javascri","errorCode":null,"errorMessage":"Your last reply was natural language, not JavaScript, so nothing ran. Reply with ONLY executable JavaScript that calls the action API — optionally inside a single ```js code block (only the code inside runs). To say something to the player, that is also code: call chat, e.g. await chat({ message: \"...\" }). Never reply in prose.","messagePattern":"Your last reply was natural language, not JavaScript, so nothing ran\\. Reply with ONLY executable JavaScript that calls the action API — optionally inside a single ```js code block \\(only the code inside runs\\)\\. To say something to the player, that is also code: call chat, e\\.g\\. await chat\\((.+?)\\)\\. Never reply in prose\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"integrations/minecraft/src/cognitive/conscious/js-planner.ts","lineNumber":724,"sourceCode":"    this.bridgeTimeoutMs = options.bridgeTimeoutMs ?? 30_000\n    this.maxBridgeCalls = options.maxBridgeCalls ?? 64\n    this.timeoutMs = options.timeoutMs ?? 750\n    this.maxActionsPerTurn = options.maxActionsPerTurn ?? 5\n    this.memoryLimitMb = options.memoryLimitMb ?? 32\n  }\n\n  public async evaluate(\n    content: string,\n    availableActions: Action[],\n    globals: RuntimeGlobals,\n    executeAction: (action: ActionInstruction) => Promise<unknown>,\n  ): Promise<JavaScriptRunResult> {\n    const script = extractJavaScriptCandidate(content)\n\n    // Firewall: reject natural-language replies before they reach the sandbox, with a directive\n    // correction the model can actually act on (instead of an opaque \"Unexpected identifier\" loop).\n    if (!isSyntacticallyValidScript(script)) {\n      throw new Error(\n        'Your last reply was natural language, not JavaScript, so nothing ran. '\n        + 'Reply with ONLY executable JavaScript that calls the action API — optionally inside a single '\n        + '```js code block (only the code inside runs). To say something to the player, that is also code: '\n        + 'call chat, e.g. await chat({ message: \"...\" }). Never reply in prose.',\n      )\n    }\n\n    const run: ActivePlannerRun = {\n      actionCount: 0,\n      actionsByName: new Map(availableActions.map(action => [action.name, action])),\n      executeAction,\n      executed: [],\n      logs: [],\n      sawSkip: false,\n    }\n\n    this.activeRun = run\n    let result: unknown","sourceCodeStart":706,"sourceCodeEnd":742,"githubUrl":"https://github.com/moeru-ai/airi/blob/27111382b4a79a7e983289d6e983a06af185ed0f/integrations/minecraft/src/cognitive/conscious/js-planner.ts#L706-L742","documentation":"Thrown by JavaScriptPlanner.evaluate() as a syntax firewall before the model's reply ever reaches the sandbox. extractJavaScriptCandidate() pulls a fenced ```js block or strips CJK prose edge-lines from the raw content; isSyntacticallyValidScript() then compiles the candidate with `new Function('return (async () => {\\n...\\n})()')` (compile-only, no execution). If that throws a SyntaxError, this directive message is raised instead of letting the isolate emit an opaque 'Unexpected identifier' death-spiral. It is a corrective instruction meant to be fed back to the LLM so its next turn is real code.","triggerScenarios":"The LLM driving the cognitive planner replies with pure prose, markdown that is not a code fence, partially-fenced code (missing closing ```), or un-fenced code prefixed by a non-CJK natural-language line that stripEdgeProseLines cannot remove (e.g. English intro line). Also fires when the candidate contains valid CJK identifiers that compile as an expression but the surrounding prose breaks syntax, or when the model emits a language other than JS inside the fence (e.g. python).","commonSituations":"Cold-start or low-temperature models that narrate intent ('Let me go mine stone.') before acting; models trained on chat formats that wrap code in explanations; prompt templates that do not strongly enforce code-only output; locale-specific models that emit Chinese/Japanese/Korean reasoning lines; a fence typo such as ``` javascript with an inner space, or a stray backtick inside the code that closes the fence early.","solutions":["Feed the message verbatim back into the LLM context as a correction and re-prompt; the message is already phrased as a directive.","Ensure the planner prompt instructs the model to reply with ONLY a single ```js fence and no surrounding prose.","If English (non-CJK) prose prefixes are common, extend stripEdgeProseLines to treat leading ASCII sentences without JS structure as prose too.","Check that the fence regex in extractJavaScriptCandidate matches the model's actual fence style (no stray inner whitespace/language tag mismatches)."],"exampleFix":"// before (model reply that triggers it):\n//   Sure! I'll go mine the stone now.\n//   await mineBlockAt({ position: { x: 10, y: 64, z: -5 } })\n//\n// after (model reply that passes the firewall):\n//   ```js\n//   await mineBlockAt({ position: { x: 10, y: 64, z: -5 } })\n//   ```","handlingStrategy":"validation","validationCode":"// Before calling evaluate(), pre-screen the model reply the same way the firewall does.\nimport { extractJavaScriptCandidate, isSyntacticallyValidScript } from './js-planner'\n\nfunction isExecutableReply(content: string): boolean {\n  const candidate = extractJavaScriptCandidate(content)\n  return isSyntacticallyValidScript(candidate)\n}\n\n// if (!isExecutableReply(reply)) { re-prompt the model for code-only output }","typeGuard":null,"tryCatchPattern":"// Planner callers should treat evaluate() as fallible and branch on the syntax-firewall error:\n// try {\n//   const result = await planner.evaluate(content, actions, globals, exec)\n// } catch (e) {\n//   if (errorMessageFrom(e)?.startsWith('Your last reply was natural language')) {\n//     // feed the directive back to the LLM and retry once\n//   } else throw e\n// }","preventionTips":["Configure the planner system prompt to demand a single fenced ```js block and nothing else.","Reject model replies containing more than one code fence before evaluation.","For CJK-heavy models, verify stripEdgeProseLines covers the prose style they emit.","Keep one retry budget for the firewall correction so a single prose reply does not fail the turn."],"tags":["llm-planner","sandbox","syntax-firewall","natural-language"],"backgroundTag":null,"analyzedSha":"27111382b4a79a7e983289d6e983a06af185ed0f","analyzedAt":"2026-08-12T18:33:34.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}