{"record":{"id":"e224caf8ee044053","repo":"vercel/ai","slug":"harness-session-this-sessionid-has-an-unfinish","errorCode":null,"errorMessage":"`Harness session ${this.sessionId} has an unfinished turn and must be continued before accepting a new prompt.`","messagePattern":"`Harness session (.+?) has an unfinished turn and must be continued before accepting a new prompt\\.`","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/harness/src/agent/harness-agent-session.ts","lineNumber":646,"sourceCode":"  }): HarnessAgentResumeSessionState {\n    const { continueFrom } = options;\n    return {\n      type: 'resume-session',\n      harnessId: continueFrom.harnessId,\n      specificationVersion: continueFrom.specificationVersion,\n      data: continueFrom.data,\n      continueFrom,\n    };\n  }\n\n  private requirePromptableTurn(): void {\n    if (this.turnState === 'idle') return;\n    if (this.turnState === 'running') {\n      throw new Error(\n        `Harness session ${this.sessionId} already has a turn in progress.`,\n      );\n    }\n    throw new Error(\n      `Harness session ${this.sessionId} has an unfinished turn and must be continued before accepting a new prompt.`,\n    );\n  }\n\n  private requireContinuableTurn(): void {\n    if (\n      this.turnState === 'awaiting-approval' ||\n      this.turnState === 'awaiting-tool-result' ||\n      this.turnState === 'suspended'\n    ) {\n      return;\n    }\n    if (this.turnState === 'running') {\n      throw new Error(\n        `Harness session ${this.sessionId} already has a turn in progress.`,\n      );\n    }\n    throw new Error(","sourceCodeStart":628,"sourceCodeEnd":664,"githubUrl":"https://github.com/vercel/ai/blob/69428b1f8b037e4d118fb4853428d5c4e620493c/packages/harness/src/agent/harness-agent-session.ts#L628-L664","documentation":"Also thrown by `requirePromptableTurn()` from `promptTurn()`, for turn states that are neither 'idle' nor 'running' — namely 'awaiting-approval', 'awaiting-tool-result', or 'suspended'. The session has an unfinished, paused turn (waiting on a tool approval, a tool result, or held as a suspended continuation), and a brand-new prompt is not allowed; the caller must continue, approve, or resume the existing turn instead.","triggerScenarios":"Calling `promptTurn()` while the session is (a) waiting for a tool approval decision (markAwaitingApprovalIfActive set 'awaiting-approval'), (b) waiting for a pending tool result to be supplied ('awaiting-tool-result'), or (c) suspended at a stop-condition boundary awaiting `continueTurn()`.","commonSituations":"Sending a fresh user message while the agent is blocked on a tool-permission prompt; dropping the approval/tool-result flow and trying to 'restart' with a new prompt; attempting a new prompt after suspending at a slice boundary instead of resuming with continueTurn; state machines that lose track of pending approvals.","solutions":["Resolve the pending tool approval (approve/reject via the session's toolApproval flow) so the turn can finish, then prompt anew.","Supply the pending tool result the turn is waiting for, letting it complete before the next prompt.","Call `continueTurn()` to resume/finish the awaiting or suspended turn instead of starting a fresh prompt.","If the paused turn is no longer wanted, stop/destroy the session and create a new session for the new prompt.","Check `hasUnfinishedTurn()` and branch: continue the unfinished turn rather than prompting."],"exampleFix":"// before\n// turn is suspended at a stop-condition boundary\nsession.promptTurn({ prompt: 'next task', ... }); // throws: unfinished turn\n\n// after\nconst cont = session.continueTurn({ ... }); // resume the unfinished turn\nawait cont.ready;\nawait cont.result;\nsession.promptTurn({ prompt: 'next task', ... }); // now allowed (turnState idle)","handlingStrategy":"validation","validationCode":"if (session.hasUnfinishedTurn()) {\n  // finish the paused turn: resolve approvals/tool results or continueTurn()\n  throw new UnfinishedTurnError();\n}","typeGuard":null,"tryCatchPattern":"try {\n  session.promptTurn({ prompt, ... });\n} catch (error) {\n  if (error instanceof Error && /must be continued before accepting a new prompt/.test(error.message)) {\n    const cont = session.continueTurn({ ... }); // resume paused/suspended turn\n    await cont.ready;\n    session.promptTurn({ prompt, ... });\n  } else {\n    throw error;\n  }\n}","preventionTips":["Wire up the tool-approval and tool-result flows so turns never stall in awaiting states.","After suspension at a slice boundary, resume with continueTurn — never a new prompt.","Check `hasUnfinishedTurn()` and branch to continue vs. prompt.","Stop/destroy and recreate the session if a paused turn is no longer wanted."],"tags":["lifecycle","state-machine","harness","tool-approval"],"backgroundTag":"invalid-state-transition","analyzedSha":"69428b1f8b037e4d118fb4853428d5c4e620493c","analyzedAt":"2026-08-30T12:32:21.016Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}