# Direct S3 upload with local store raises NoMethodError instead of being rejected

**URL:** https://meta.discourse.org/t/direct-s3-upload-with-local-store-raises-nomethoderror-instead-of-being-rejected/412061
**Category:** Bug
**Tags:** s3
**Created:** [10 september 2026 om 04:06 UTC](https://meta.discourse.org/t/direct-s3-upload-with-local-store-raises-nomethoderror-instead-of-being-rejected/412061 "2026-09-10T04:06:34Z")
**Posts on this page:** 3
**Page:** 1

<div class="post-metadata">

### Author: ![RGJ](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/rgj/32/523185_2.png) [@RGJ](https://meta.discourse.org/u/RGJ)
#### Post date: [10 september 2026 om 04:06 UTC](https://meta.discourse.org/t/direct-s3-upload-with-local-store-raises-nomethoderror-instead-of-being-rejected/412061/1 "2026-09-10T04:06:34Z")

</div>

I found and resolved this issue myself after a client reported it. I used AI to generate the bug report because I was lazy and it does a better job than I do - and it found a core bug while doing that, which I manually verified.

Nothing big but it might make sense to see if this pattern is causing issues in other places as well.

It all started with someone having checked `enable_direct_s3_uploads` without actually configuring and enabling S3, which broke all uploads, returning 500 errors behind the scenes. I thought the 500 was worth investigating.

## Summary

When `enable_direct_s3_uploads` is enabled while the active upload store is local, attempting an upload can raise:

```plaintext
NoMethodError (undefined method 'signed_request_for_temporary_upload' for an instance of FileStore::LocalStore)

app/services/external_upload_manager.rb:34:in 'ExternalUploadManager.create_direct_upload'
lib/external_upload_helpers.rb:56:in 'ExternalUploadHelpers#generate_presigned_put'
app/controllers/application_controller.rb:443:in 'block in ApplicationController#with_resolved_locale'
app/controllers/application_controller.rb:443:in 'ApplicationController#with_resolved_locale'

```

This appears to be caused by a combination of an invalid settings state and an overwritten `before_action` callback.

## Configuration

The problematic state is effectively:

```ruby
SiteSetting.enable_direct_s3_uploads == true
SiteSetting.enable_s3_uploads == false
Discourse.store.is_a?(FileStore::LocalStore) == true

```

For local storage, `Discourse.store` does not implement:

```ruby
signed_request_for_temporary_upload

```

which is expected, since that operation only makes sense for an external/S3 store.

## Expected behaviour

If direct S3 uploads are enabled while the active store is local, the request should be rejected cleanly by `external_store_check`.

Ideally the invalid site setting combination should also be prevented or validated.

## Actual behaviour

The request reaches `ExternalUploadManager.create_direct_upload`, which calls:

```ruby
store.signed_request_for_temporary_upload(...)

```

on a `FileStore::LocalStore`, resulting in a `NoMethodError`.

## Possible cause

`ExternalUploadHelpers` registers the following callback:

```ruby
before_action :external_store_check,
  only: %i[
    generate_presigned_put
    complete_external_upload
    create_multipart
    batch_presign_multipart_parts
    complete_multipart
    abort_multipart
  ]

```

This should prevent the request from reaching the external upload code when the store is local.

However, `UploadsController` later registers another callback using the same filter method:

```ruby
before_action :external_store_check,
  only: %i[_show_secure_deprecated show_secure]

```

[Rails treats repeated registration of the same callback as a redefinition](https://guides.rubyonrails.org/action_controller_overview.html#before-action), so the latter appears to replace the earlier `only:` conditions.

![image](https://global.discourse-cdn.com/meta/original/4X/5/8/c/58cc0a10a6c08d87b1905931451044a5ca4a167f.png)

As a result, `external_store_check` is no longer run for `generate_presigned_put`, allowing a local store to reach the direct-upload code path.

## Reproduction

1. Configure a Discourse instance with local upload storage.
2. Ensure:

```ruby
SiteSetting.enable_s3_uploads = false
SiteSetting.enable_direct_s3_uploads = true

```

1. Attempt to upload a file.
2. Observe the `NoMethodError` from `FileStore::LocalStore`.

A console check of the state can be done with:

```ruby
[
  SiteSetting.enable_direct_s3_uploads,
  SiteSetting.enable_s3_uploads,
  Discourse.store.class,
  Discourse.store.external?
]

```

## Suggested fix

The callback registrations should not overwrite each other.

For example, include the secure upload actions in the existing `external_store_check` callback:

```ruby
before_action :external_store_check,
  only: %i[
    generate_presigned_put
    complete_external_upload
    create_multipart
    batch_presign_multipart_parts
    complete_multipart
    abort_multipart
    _show_secure_deprecated
    show_secure
  ]

```

Alternatively, use a separate callback method for the secure-upload actions.

It may also be worth adding validation so that `enable_direct_s3_uploads` cannot be enabled while S3/external uploads are disabled.

## Workaround

For sites using local upload storage:

```ruby
SiteSetting.enable_direct_s3_uploads = false

```

prevents the error.

---

<div class="post-metadata">

### Author: ![sam](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/sam/32/102149_2.png) [@sam](https://meta.discourse.org/u/sam)
#### Post date: [10 september 2026 om 07:08 UTC](https://meta.discourse.org/t/direct-s3-upload-with-local-store-raises-nomethoderror-instead-of-being-rejected/412061/2 "2026-09-10T07:08:53Z")

</div>

Thanks, should be fixed per:

> <https://github.com/discourse/discourse/pull/43505>
>
> Prevent secure upload callbacks from replacing the store check for
> external uplo…ad actions. Presigned upload requests must return 404
> when S3 storage is disabled, even if direct S3 uploads are enabled.
> 
> Add regression coverage for this configuration.

---

<div class="post-metadata">

### Author: ![sam](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/sam/32/102149_2.png) [@sam](https://meta.discourse.org/u/sam)
#### Post date: [11 september 2026 om 22:00 UTC](https://meta.discourse.org/t/direct-s3-upload-with-local-store-raises-nomethoderror-instead-of-being-rejected/412061/4 "2026-09-11T22:00:51Z")

</div>

This topic was automatically closed after 38 hours. New replies are no longer allowed.
