{"record":{"id":"a3c7d5c2e6446d99","repo":"can1357/oh-my-pi","slug":"github-release-asset-binaryname-is-not-fully-up","errorCode":null,"errorMessage":"GitHub release asset ${binaryName} is not fully uploaded","messagePattern":"GitHub release asset (.+?) is not fully uploaded","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/cli/update-cli.ts","lineNumber":219,"sourceCode":"\t}\n\tif (release.draft !== false) {\n\t\tthrow new Error(`GitHub release ${expectedTag} is a draft, not a published release`);\n\t}\n\tif (release.prerelease !== false && !options.allowPrerelease) {\n\t\tthrow new Error(`GitHub release ${expectedTag} is a prerelease; only canary updates install prerelease assets`);\n\t}\n\tif (!Array.isArray(release.assets)) {\n\t\tthrow new Error(`GitHub release ${expectedTag} has no asset list`);\n\t}\n\n\tconst matches = release.assets.filter(asset => isRecord(asset) && asset.name === binaryName);\n\tif (matches.length !== 1) {\n\t\tthrow new Error(`GitHub release ${expectedTag} has ${matches.length} assets named ${binaryName}`);\n\t}\n\n\tconst asset = matches[0];\n\tif (!isRecord(asset) || asset.state !== \"uploaded\") {\n\t\tthrow new Error(`GitHub release asset ${binaryName} is not fully uploaded`);\n\t}\n\tif (typeof asset.size !== \"number\" || !Number.isSafeInteger(asset.size) || asset.size <= 0) {\n\t\tthrow new Error(`GitHub release asset ${binaryName} has an invalid size`);\n\t}\n\tif (typeof asset.digest !== \"string\") {\n\t\tthrow new Error(`GitHub release asset ${binaryName} has no digest`);\n\t}\n\tconst digest = /^sha256:([0-9a-f]{64})$/i.exec(asset.digest)?.[1];\n\tif (!digest) {\n\t\tthrow new Error(`GitHub release asset ${binaryName} has an unsupported digest`);\n\t}\n\n\tconst expectedUrl = `https://github.com/${REPO}/releases/download/${expectedTag}/${binaryName}`;\n\tif (asset.browser_download_url !== expectedUrl) {\n\t\tthrow new Error(`GitHub release asset ${binaryName} has an unexpected download URL`);\n\t}\n\n\treturn {","sourceCodeStart":201,"sourceCodeEnd":237,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/cli/update-cli.ts#L201-L237","documentation":"GitHub assets expose a `state` field; only `uploaded` means the asset transfer completed and is served publicly. The updater refuses to download an asset that is still being uploaded (`start`/`new`/`uploading`) or whose record is malformed, because a partial binary would fail integrity verification anyway.","triggerScenarios":"resolveReleaseBinaryAsset throws when the matched asset fails `isRecord(asset) || asset.state !== \"uploaded\"` — the asset state is anything other than \"uploaded\" or the asset entry is not an object.","commonSituations":"Running the updater seconds after a release is published while GitHub is still finalizing asset uploads, a release workflow that published the release before all uploads finished, or a failed/interrupted upload leaving the asset in a non-uploaded state.","solutions":["Wait a minute or two for the asset upload to complete, then retry the update.","Check the release page for a stuck/failed upload; delete and re-upload the affected asset.","If a workflow publishes before uploading, fix it to upload all assets before the publish step.","Verify asset state via the API: curl https://api.github.com/repos/<owner>/<repo>/releases/tags/<tag> and check assets[].state."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"const asset = release.assets?.find(a => a?.name === binaryName);\nif (asset && asset.state !== \"uploaded\") {\n  console.log(\"Asset still uploading; waiting 30s...\");\n  await Bun.sleep(30_000);\n}","typeGuard":"function isUploadedAsset(asset: unknown): asset is { state: \"uploaded\"; size: number; digest: string; browser_download_url: string } {\n  return typeof asset === \"object\" && asset !== null && (asset as { state?: unknown }).state === \"uploaded\";\n}","tryCatchPattern":"for (let attempt = 0; attempt < 3; attempt++) {\n  try { await update(); break; }\n  catch (err) {\n    if (err instanceof Error && err.message.includes(\"not fully uploaded\")) {\n      await Bun.sleep(30_000);\n      continue;\n    }\n    throw err;\n  }\n}","preventionTips":["Wait a short grace period after a release is announced before running updates.","Ensure release workflows upload all assets before flipping the release from draft to published.","Alert on failed upload steps in release CI so assets are never left partial."],"tags":["github-api","release-assets","update","transient"],"backgroundTag":"asset-upload-incomplete","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}