{"record":{"id":"2ba56f254fe5fc67","repo":"tinyhumansai/openhuman","slug":"method-resourcepath-failed-message","errorCode":null,"errorMessage":"${method} ${resourcePath} failed: ${message}","messagePattern":"(.+?) (.+?) failed: (.+?)","errorType":"http","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/ios-appstore-metadata.mjs","lineNumber":82,"sourceCode":") {\n  const res = await fetch(`${apiBase}${resourcePath}`, {\n    method,\n    headers: {\n      Authorization: `Bearer ${jwt}`,\n      Accept: \"application/json\",\n      ...(body && !raw ? { \"Content-Type\": \"application/json\" } : {}),\n      ...headers,\n    },\n    body: body ? (raw ? body : JSON.stringify(body)) : undefined,\n  });\n  const text = await res.text();\n  const payload = text ? JSON.parse(text) : null;\n  if (!res.ok) {\n    const message =\n      payload?.errors\n        ?.map((e) => `${e.status} ${e.code}: ${e.detail}`)\n        .join(\"\\n\") || text;\n    throw new Error(`${method} ${resourcePath} failed: ${message}`);\n  }\n  return payload;\n}\n\nasync function uploadOperation(operation, fileBuffer) {\n  const headers = Object.fromEntries(\n    (operation.requestHeaders || []).map((h) => [h.name, h.value]),\n  );\n  const offset = Number(operation.offset || 0);\n  const length = Number(operation.length || fileBuffer.length);\n  const chunk = fileBuffer.subarray(offset, offset + length);\n  const res = await fetch(operation.url, {\n    method: operation.method,\n    headers,\n    body: chunk,\n  });\n  if (!res.ok) {\n    throw new Error(","sourceCodeStart":64,"sourceCodeEnd":100,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/a221052e0df5b1f7598fceba7329fd1af95d6699/scripts/ios-appstore-metadata.mjs#L64-L100","documentation":"The request() helper wraps fetch against https://api.appstoreconnect.apple.com/v1 and, on any non-2xx, formats the App Store Connect error envelope (payload.errors[] of {status, code, detail}) into the thrown message. So this error's text is ASC's own diagnosis: HTTP status plus machine-readable code plus human detail, one line per error — read it before changing anything.","triggerScenarios":"401 NOT_AUTHORIZED from an expired/revoked key or wrong issuer id; 404 NOT_FOUND from a bad ASC_APP_ID or resource id in the URL; 409 ENTITY_ERROR such as a duplicate locale or validation on metadata fields; 429 RATE_LIMITED when hammering the API during screenshot uploads.","commonSituations":"The .p8 was regenerated in ASC (old key id revoked) but ASC_KEY_ID still names the old key; ASC_APP_ID points at an app in another team; metadata text files under fastlane/metadata/en-US exceed length limits; CI retrying the whole script and tripping rate limits.","solutions":["Read each `status code: detail` line in the message — the ASC code names the exact problem (NOT_AUTHORIZED vs NOT_FOUND vs ENTITY_ERROR need opposite fixes)","401: verify ASC_KEY_ID matches the .p8 filename, the issuer id is the team issuer shown in ASC → Integrations, and the key has not been revoked; re-run to mint a fresh JWT","404: confirm the numeric ASC_APP_ID (App Store Connect → App → the id in the URL) and that the app/appStoreVersion exists","409/422: fix the named field in the fastlane/metadata/en-US text files per the detail line","429: wait and re-run; the script is idempotent per-run, so a clean retry after the rate window resolves it"],"exampleFix":"# before — old revoked key id still exported:\nexport ASC_KEY_ID=\"OLDKEY0001\"\n\n# after — key id matches the newly downloaded .p8:\nexport ASC_KEY_ID=\"NEWKEY9999\"   # AuthKey_NEWKEY9999.p8","handlingStrategy":"try-catch","validationCode":"// Cheap smoke test before the full run: GET /apps/{id} with the same JWT\n// A 200 proves key id + issuer + .p8 are valid and the app is visible.\nconst res = await fetch(`https://api.appstoreconnect.apple.com/v1/apps/${appId}`, {\n  headers: { Authorization: `Bearer ${jwt}` },\n});\nif (res.status === 401) { console.error('ASC key invalid/revoked — fix credentials before the real run'); process.exit(1); }\nif (res.status === 404) { console.error(`ASC_APP_ID ${appId} not visible to this key`); process.exit(1); }","typeGuard":null,"tryCatchPattern":"try {\n  await request('POST', '/appInfoLocalizations', payload);\n} catch (err) {\n  // Message lines look like '409 ENTITY_ERROR: ...' — classify before retrying\n  const lines = err.message.split('\\n');\n  const unauthorized = lines.some(l => l.startsWith('401 '));\n  const rateLimited = lines.some(l => l.startsWith('429 '));\n  if (unauthorized) throw new Error('ASC credentials rejected — check ASC_KEY_ID/ASC_ISSUER_ID/ASC_KEY_PATH');\n  if (rateLimited) { await sleep(60_000); return retry(); }\n  throw err; // 404/409/422 are config/validation — read the detail lines\n}","preventionTips":["Always read the `status code: detail` lines ASC returns — the code (NOT_AUTHORIZED, NOT_FOUND, ENTITY_ERROR) dictates the fix","Run the /apps/{id} GET smoke test before long screenshot uploads so auth problems surface in seconds, not after minutes of work","Keep metadata text files within ASC field length limits and lint them before upload to avoid 409/422 validation errors"],"tags":["app-store-connect","api","network","auth","release"],"backgroundTag":null,"analyzedSha":"a221052e0df5b1f7598fceba7329fd1af95d6699","analyzedAt":"2026-08-16T12:47:06.542Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}