{"record":{"id":"1ce4f9ec1b4f64de","repo":"paperclipai/paperclip","slug":"opencode-thread-is-not-open","errorCode":null,"errorMessage":"OpenCode thread is not open","messagePattern":"OpenCode thread is not open","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/paperclip-runner/src/cli/opencode-app-server-proxy.ts","lineNumber":347,"sourceCode":"  let result: unknown;\n  switch (message.method) {\n    case \"initialize\":\n      result = {\n        user: { sessionId: \"opencode\" },\n        serverInfo: { name: \"opencode\", version: \"1.18.29\" },\n      };\n      break;\n    case \"thread/start\":\n      result = await open(params, false);\n      break;\n    case \"thread/resume\":\n      result = await open(params, true);\n      break;\n    case \"collaborationMode/list\":\n      result = openCodeProxyCollaborationModes(activeModel);\n      break;\n    case \"turn/start\": {\n      if (!session) throw new Error(\"OpenCode thread is not open\");\n      assertOpenCodeProxyCollaborationMode(params);\n      const inputItems = Array.isArray(params.input)\n        ? params.input.map(record)\n        : [];\n      const messageText = inputItems\n        .map((entry) => text(entry.text))\n        .filter(Boolean)\n        .join(\"\\n\");\n      const turn = await session.startTurn({\n        message: { role: \"user\", text: messageText },\n      });\n      activeTurnId = turn.turnId;\n      // OpenCode normally publishes session/turn startup over SSE, but a fast\n      // completion can make the synchronous prompt response the only place the\n      // authoritative turn id is observed. Emit the normalized notification\n      // after this request's JSON-RPC response when SSE did not already announce\n      // it. runnerd buffers notifications received while awaiting a response,\n      // so replaying an earlier SSE announcement would violate strict turn","sourceCodeStart":329,"sourceCodeEnd":365,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/src/cli/opencode-app-server-proxy.ts#L329-L365","documentation":"The OpenCode app-server proxy translates JSON-RPC requests (runnerd protocol) into calls against an OpenCode driver session. 'turn/start' requires an open thread/session; the proxy throws this error when a turn is requested before 'thread/start' or 'thread/resume' succeeded (or after the session was never opened in this process).","triggerScenarios":"Sending a JSON-RPC 'turn/start' request to the proxy without first sending 'thread/start' or 'thread/resume', or after a prior 'thread/start' failed and left `session` null.","commonSituations":"Client reconnects and skips the initialize/thread handshake; a bootstrap failure (e.g. missing opencode binary) means the thread never opened but the client retries turns; test harness drives turn/start directly.","solutions":["Send 'thread/start' (or 'thread/resume' with the existing thread id) before any 'turn/start'.","Check proxy stderr for bootstrap failures (missing/misconfigured opencode CLI) that prevented the session from opening.","Retry thread creation first, then re-issue the turn request.","If resuming an existing conversation, use 'thread/resume' with the prior driver session id instead of assuming state persists."],"exampleFix":"// before\nawait rpc({ method: 'turn/start', params: { input: [{ text: 'hi' }] } });\n// after\nawait rpc({ method: 'thread/start', params: { cwd } });\nawait rpc({ method: 'turn/start', params: { input: [{ text: 'hi' }] } });","handlingStrategy":"try-catch","validationCode":"let threadOpen = false;\nasync function ensureThread(params) {\n  if (!threadOpen) {\n    await rpc({ method: 'thread/start', params });\n    threadOpen = true;\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  await rpc({ method: 'turn/start', params });\n} catch (e) {\n  if (e.message.includes('thread is not open')) {\n    await rpc({ method: 'thread/start', params: threadParams });\n    await rpc({ method: 'turn/start', params });\n  } else throw e;\n}","preventionTips":["Always run the initialize + thread/start handshake before issuing turn commands.","Track session-open state in the client and re-open after any proxy restart.","Monitor proxy stderr for bootstrap failures instead of blindly retrying turns."],"tags":["json-rpc","state","session"],"backgroundTag":"invalid-state-transition","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}