{"record":{"id":"45bc997c24021bae","repo":"gitbutlerapp/gitbutler","slug":"the-refreshed-reviews-were-missing-from-the-respon","errorCode":null,"errorMessage":"The refreshed reviews were missing from the response.","messagePattern":"The refreshed reviews were missing from the response\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"packages/but-mcp-app/src/ReviewApp.tsx","lineNumber":349,"sourceCode":"\t\tconst reviewNumbers = view.reviews.filter(shouldPollReview).map((review) => review.number);\n\t\tif (reviewNumbers.length === 0) return;\n\n\t\tlet cancelled = false;\n\n\t\tasync function refreshReviews() {\n\t\t\ttry {\n\t\t\t\tconst result = await app?.callServerTool({\n\t\t\t\t\tname: \"gitbutler_refresh_reviews\",\n\t\t\t\t\targuments: {\n\t\t\t\t\t\trepository: view?.repository.path,\n\t\t\t\t\t\treviewNumbers,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t\tif (cancelled || result === undefined) return;\n\t\t\t\tif (result.isError) throw new Error(textFromToolResult(result));\n\t\t\t\tconst refreshed = reviewViewFromToolResult(result);\n\t\t\t\tif (refreshed === null) {\n\t\t\t\t\tthrow new Error(\"The refreshed reviews were missing from the response.\");\n\t\t\t\t}\n\t\t\t\tsetView((current) => (current === null ? current : mergeReviewViews(current, refreshed)));\n\t\t\t} catch (pollCause) {\n\t\t\t\tif (cancelled) return;\n\t\t\t\tsetPollingError(\n\t\t\t\t\tpollCause instanceof Error ? pollCause.message : \"Could not refresh CI status.\",\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tconst timeout = window.setTimeout(() => void refreshReviews(), REVIEW_POLL_INTERVAL_MS);\n\t\treturn () => {\n\t\t\tcancelled = true;\n\t\t\twindow.clearTimeout(timeout);\n\t\t};\n\t}, [app, pollingError, view]);\n\n\tif (error !== null) {","sourceCodeStart":331,"sourceCodeEnd":367,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/2497b8007aa4a1922dae9a805b32ffe5b5037785/packages/but-mcp-app/src/ReviewApp.tsx#L331-L367","documentation":"Thrown inside the 30-second review-refresh poll in packages/but-mcp-app/src/ReviewApp.tsx:349. The gitbutler_refresh_reviews tool call returned without isError, but reviewViewFromToolResult returned null — the result's structuredContent is not an object carrying repository, forge, and reviews keys. The local catch converts it to a polling-error banner; the next interval retries automatically.","triggerScenarios":"Calling gitbutler_refresh_reviews with {repository, reviewNumbers} where the tool version returns text-only content or a changed schema (structuredContent missing repository/forge/reviews); version skew between the but-mcp-app bundle and the server tool.","commonSituations":"Desktop app or but CLI updated independently of the MCP app; an MCP host that strips or rewrites structuredContent; schema evolution of the review payload between releases.","solutions":["Update the but CLI/desktop component so the tool emits the expected structuredContent","Log result.structuredContent once to see exactly which key is missing","Ship tool and UI in lockstep — bump both together in the same release","If the keys were legitimately renamed, update reviewViewFromToolResult to the new schema"],"exampleFix":"// before\nconst refreshed = reviewViewFromToolResult(result);\nif (refreshed === null) {\n\tthrow new Error(\"The refreshed reviews were missing from the response.\");\n}\n\n// after — name the missing keys to make the mismatch diagnosable\nconst refreshed = reviewViewFromToolResult(result);\nif (refreshed === null) {\n\tconst keys = result.structuredContent ? Object.keys(result.structuredContent).join(\", \") : \"none\";\n\tthrow new Error(`The refreshed reviews were missing from the response (structuredContent keys: ${keys}).`);\n}","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"function isReviewView(v: unknown): v is ReviewView {\n\treturn (\n\t\ttypeof v === \"object\" &&\n\t\tv !== null &&\n\t\t\"repository\" in v &&\n\t\t\"forge\" in v &&\n\t\t\"reviews\" in v\n\t);\n}","tryCatchPattern":"// the poll already contains the right shape: keep failures non-fatal and let the next tick retry\ntry {\n\tconst refreshed = reviewViewFromToolResult(result);\n\tif (refreshed === null) throw new Error(\"The refreshed reviews were missing from the response.\");\n\tsetView((current) => (current === null ? current : mergeReviewViews(current, refreshed)));\n} catch (pollCause) {\n\tif (cancelled) return;\n\tsetPollingError(pollCause instanceof Error ? pollCause.message : \"Could not refresh CI status.\");\n}","preventionTips":["Ship the MCP tool and the app UI in the same release so schemas stay aligned","Log structuredContent shape once on mismatch to identify the missing key","Treat poll failures as transient — the 30s interval retries automatically","Never let a poll error unmount the view; keep last-good data rendered"],"tags":["mcp","reviews","polling","schema-validation"],"backgroundTag":"mcp-structured-content-missing","analyzedSha":"2497b8007aa4a1922dae9a805b32ffe5b5037785","analyzedAt":"2026-08-17T00:30:25.648Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}