{"record":{"id":"1db3be8e4f3f1204","repo":"transloadit/uppy","slug":"missing-s3-object-key-for-resuming-upload","errorCode":null,"errorMessage":"Missing S3 object key for resuming upload","messagePattern":"Missing S3 object key for resuming upload","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/@uppy/aws-s3/src/S3Uploader.ts","lineNumber":242,"sourceCode":"      }\n      this.#options.s3Client\n        .abortMultipartUpload({ key: this.#key, uploadId: this.#uploadId })\n        .catch((abortErr) => {\n          this.#options.log?.(abortErr, 'warning')\n        })\n    }\n\n    this.#key = undefined\n    this.#uploadId = undefined\n    this.#uploadHasStarted = false\n  }\n\n  async #resumeMultipartUpload(\n    uploadId: string,\n    signal: AbortSignal,\n  ): Promise<void> {\n    if (!this.#key) {\n      throw new Error('Missing S3 object key for resuming upload')\n    }\n    const existingParts = await this.#options.s3Client.listParts({\n      uploadId,\n      key: this.#key,\n      signal,\n    })\n    // Sync local state with S3 - mark already-uploaded parts\n    for (const part of existingParts) {\n      const chunkIndex = part.partNumber - 1\n      if (chunkIndex >= 0 && chunkIndex < this.#chunkState.length) {\n        this.#chunkState[chunkIndex].uploaded = this.#chunks[chunkIndex].size\n        this.#chunkState[chunkIndex].etag = part.etag\n      }\n    }\n    // Emit progress update to reflect already-uploaded parts\n    this.#onProgress()\n    await this.#uploadRemainingParts(signal)\n  }","sourceCodeStart":224,"sourceCodeEnd":260,"githubUrl":"https://github.com/transloadit/uppy/blob/5d4dedd02a1ac0ae022c75c54aca76558f88e256/packages/@uppy/aws-s3/src/S3Uploader.ts#L224-L260","documentation":"During the OAuth callback, Companion failed to verify the state/origin or exchange the code for tokens, so it responds 400 with an HTML error page (either the modern auth-callback page keyed by authCallbackToken, or a legacy HTML page for old Uppy clients).","triggerScenarios":"The state cookie/signature does not match (expired or cross-origin state), the authorization code was reused or expired, grant's token exchange failed, or the origin in the state does not match the requesting origin (origin mismatch protection).","commonSituations":"User sits on the OAuth consent screen too long and the state expires; cookies blocked by the browser so state cannot be round-tripped; companionOptions.server.oauthDomain or origins misconfigured so the origin check fails; retrying/refreshing an already-consumed callback URL.","solutions":["Retry the connect flow from the beginning (get a fresh state and authorization code)","Verify companion options `server.origins` includes the exact frontend origin (scheme, host, port)","Ensure cookies are not blocked and the Companion domain matches between the redirect and the callback","Check server clock and state expiry (authStateExpiry) configuration"],"exampleFix":"// before\ncompanion({ server: { origins: ['https://wrong.example.com'] } })\n// after\ncompanion({ server: { origins: ['https://app.example.com'] } })","handlingStrategy":"retry","validationCode":"// before redirecting to connect, confirm the frontend origin is registered\nconst allowed = companionOrigins.includes(window.location.origin)\nif (!allowed) throw new Error('origin not allowed by Companion')","typeGuard":null,"tryCatchPattern":"try {\n  await startOAuthFlow()\n} catch (e) {\n  if (e.status === 400) return restartOAuthFlow() // fresh state\n  throw e\n}","preventionTips":["List every frontend origin (scheme+host+port) in companion server.origins","Do not block Companion cookies; use same-site rules that allow the OAuth round-trip","Never bookmark or replay callback URLs; always start from /connect"],"tags":["companion","oauth","state-mismatch","http-400"],"backgroundTag":"oauth-state-validation-failed","analyzedSha":"5d4dedd02a1ac0ae022c75c54aca76558f88e256","analyzedAt":"2026-08-28T12:18:41.267Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}