{"record":{"id":"97a36d700c81576b","repo":"transloadit/uppy","slug":"missing-s3-object-key-for-uploading-part","errorCode":null,"errorMessage":"Missing S3 object key for uploading part","messagePattern":"Missing S3 object key for uploading part","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/@uppy/aws-s3/src/S3Uploader.ts","lineNumber":312,"sourceCode":"    this.#options.uppy.setFileState(this.#options.file.id, {\n      s3Multipart: { uploadId, key },\n    })\n\n    await this.#uploadRemainingParts(signal)\n  }\n\n  async #uploadRemainingParts(signal: AbortSignal): Promise<void> {\n    for (let i = 0; i < this.#chunks.length; i++) {\n      signal.throwIfAborted()\n      if (this.#chunkState[i].etag) continue // already uploaded\n\n      const chunk = this.#chunks[i]\n      const partNumber = i + 1\n      const chunkData = this.#data.slice(chunk.start, chunk.end)\n      const chunkIndex = i // Capture for closure (cannot use for-loop variable i directly in a closure)\n\n      if (this.#key == null) {\n        throw new Error('Missing S3 object key for uploading part')\n      }\n      const { etag } = await this.#options.s3Client.uploadPart({\n        key: this.#key,\n        uploadId: this.#uploadId!,\n        data: chunkData,\n        partNumber,\n        onProgress: (bytesUploaded: number) => {\n          this.#chunkState[chunkIndex].uploaded = bytesUploaded\n          this.#onProgress()\n        },\n        signal,\n      })\n\n      // after part finished uploading, update chunk state\n      this.#chunkState[i].uploaded = chunk.size\n      this.#chunkState[i].etag = etag\n      this.#onProgress()\n","sourceCodeStart":294,"sourceCodeEnd":330,"githubUrl":"https://github.com/transloadit/uppy/blob/5d4dedd02a1ac0ae022c75c54aca76558f88e256/packages/@uppy/aws-s3/src/S3Uploader.ts#L294-L330","documentation":"The callback completed token exchange but req.companion.providerClass is falsy — the provider named in the URL is not registered/known to this Companion instance at this stage of the flow. Companion refuses to continue with 400 rather than crash.","triggerScenarios":"providerName in the callback URL does not match any enabled provider (e.g. /connect/unknownprov/callback), or the provider was configured via grant but its Companion provider class was not loaded/enabled (missing key/secret so it was filtered out).","commonSituations":"Enabling a provider in grant config without providing its Companion options (key/secret); a typo in the custom provider name; upgrading Companion where a provider was renamed or moved behind a flag.","solutions":["Confirm the provider is fully configured in companionOptions.providers plus its key/secret","Check for typos in the providerName in both the frontend Uppy config and the callback URL","Restart Companion after adding provider configuration","If using a custom provider, verify it is properly registered in the providers map"],"exampleFix":"// before\ncompanion({ providers: ['drive'], providerOptions: {} })\n// after\ncompanion({ providers: ['drive'], providerOptions: { drive: { key: '...', secret: '...' } } })","handlingStrategy":"validation","validationCode":"const configured = ['drive', 'dropbox', 'box'] // mirror companionOptions.providers\nif (!configured.includes(providerName)) throw new Error(`provider ${providerName} not enabled`)","typeGuard":"const isConfiguredProvider = (p: string): boolean =>\n  configuredProviders.includes(p)","tryCatchPattern":null,"preventionTips":["Keep a single shared list of enabled providers for client and server","Fail fast at app startup if a required provider lacks key/secret","Smoke-test /connect/:provider after config changes"],"tags":["companion","oauth","provider-not-registered","http-400"],"backgroundTag":"provider-not-configured","analyzedSha":"5d4dedd02a1ac0ae022c75c54aca76558f88e256","analyzedAt":"2026-08-28T12:18:41.267Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}