tinyhumansai/openhuman · error · Error
${method} ${resourcePath} failed: ${message}
Error message
${method} ${resourcePath} failed: ${message} What it means
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.
Source
Thrown at scripts/ios-appstore-metadata.mjs:82
) {
const res = await fetch(`${apiBase}${resourcePath}`, {
method,
headers: {
Authorization: `Bearer ${jwt}`,
Accept: "application/json",
...(body && !raw ? { "Content-Type": "application/json" } : {}),
...headers,
},
body: body ? (raw ? body : JSON.stringify(body)) : undefined,
});
const text = await res.text();
const payload = text ? JSON.parse(text) : null;
if (!res.ok) {
const message =
payload?.errors
?.map((e) => `${e.status} ${e.code}: ${e.detail}`)
.join("\n") || text;
throw new Error(`${method} ${resourcePath} failed: ${message}`);
}
return payload;
}
async function uploadOperation(operation, fileBuffer) {
const headers = Object.fromEntries(
(operation.requestHeaders || []).map((h) => [h.name, h.value]),
);
const offset = Number(operation.offset || 0);
const length = Number(operation.length || fileBuffer.length);
const chunk = fileBuffer.subarray(offset, offset + length);
const res = await fetch(operation.url, {
method: operation.method,
headers,
body: chunk,
});
if (!res.ok) {
throw new Error(View on GitHub (pinned to a221052e0d)
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
Example fix
# before — old revoked key id still exported: export ASC_KEY_ID="OLDKEY0001" # after — key id matches the newly downloaded .p8: export ASC_KEY_ID="NEWKEY9999" # AuthKey_NEWKEY9999.p8
Defensive patterns
Strategy: try-catch
Validate before calling
// Cheap smoke test before the full run: GET /apps/{id} with the same JWT
// A 200 proves key id + issuer + .p8 are valid and the app is visible.
const res = await fetch(`https://api.appstoreconnect.apple.com/v1/apps/${appId}`, {
headers: { Authorization: `Bearer ${jwt}` },
});
if (res.status === 401) { console.error('ASC key invalid/revoked — fix credentials before the real run'); process.exit(1); }
if (res.status === 404) { console.error(`ASC_APP_ID ${appId} not visible to this key`); process.exit(1); } Try / catch
try {
await request('POST', '/appInfoLocalizations', payload);
} catch (err) {
// Message lines look like '409 ENTITY_ERROR: ...' — classify before retrying
const lines = err.message.split('\n');
const unauthorized = lines.some(l => l.startsWith('401 '));
const rateLimited = lines.some(l => l.startsWith('429 '));
if (unauthorized) throw new Error('ASC credentials rejected — check ASC_KEY_ID/ASC_ISSUER_ID/ASC_KEY_PATH');
if (rateLimited) { await sleep(60_000); return retry(); }
throw err; // 404/409/422 are config/validation — read the detail lines
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- asset upload failed: ${res.status} ${res.statusText} ${await
- [transport:cloud] HTTP ${response.status}: ${text || respons
- gh failed (${r.status}): ${r.stderr?.trim() || "unknown erro
- gh api failed (${list.status}): ${list.stderr?.trim() || "un
- ASC_KEY_ID, ASC_ISSUER_ID, and ASC_KEY_PATH are required.
AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16).
Data as JSON: /api/errors/2ba56f254fe5fc67.
Report an issue: GitHub.