클라이언트가 보고한 문제를 제가 직접 찾아서 해결했습니다. 게을렀고, AI가 저보다 더 잘할 것 같아서 버그 리포트를 AI로 생성했는데, 그 과정에서 코어 버그를 발견했고, 이를 수동으로 검증했습니다.
큰 문제는 아니지만, 이 패턴이 다른 곳에서도 문제를 일으키고 있는지 확인하는 것이 의미가 있을 수 있습니다.
모든 것은 누군가가 S3를 실제로 구성하고 활성화하지도 않은 채 enable_direct_s3_uploads를 체크하면서 시작되었습니다. 이로 인해 모든 업로드가 깨져서 백그라운드에서 500 에러가 반환되었습니다. 500 에러가 조사할 가치가 있다고 생각했습니다.
요약
활성 업로드 스토어가 로컬인 상태에서 enable_direct_s3_uploads가 활성화되어 있으면 업로드를 시도할 때 다음과 같은 예외가 발생할 수 있습니다:
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'
이것은 잘못된 설정 상태와 덮어쓰기된 before_action 콜백의 조합으로 인해 발생하는 것으로 보입니다.
구성
문제가 되는 상태는 실질적으로 다음과 같습니다:
SiteSetting.enable_direct_s3_uploads == true
SiteSetting.enable_s3_uploads == false
Discourse.store.is_a?(FileStore::LocalStore) == true
로컬 스토리지의 경우 Discourse.store는 다음을 구현하지 않습니다:
signed_request_for_temporary_upload
이는 해당 작업이 외부/S3 스토어에서만 의미가 있기 때문에 예상되는 동작입니다.
기대되는 동작
활성 스토어가 로컬인 상태에서 직접 S3 업로드가 활성화되어 있으면, 요청은 external_store_check에 의해 깔끔하게 거부되어야 합니다.
이상적으로는 잘못된 사이트 설정 조합도 방지하거나 검증되어야 합니다.
실제 동작
요청이 ExternalUploadManager.create_direct_upload에 도달하며, 여기서 다음을 호출합니다:
store.signed_request_for_temporary_upload(...)
FileStore::LocalStore에 대해 이 메서드가 호출되어 NoMethodError가 발생합니다.
가능한 원인
ExternalUploadHelpers는 다음 콜백을 등록합니다:
before_action :external_store_check,
only: %i[
generate_presigned_put
complete_external_upload
create_multipart
batch_presign_multipart_parts
complete_multipart
abort_multipart
]
이는 스토어가 로컬일 때 요청이 외부 업로드 코드에 도달하는 것을 방지해야 합니다.
그러나 UploadsController는 나중에 동일한 필터 메서드를 사용하여 또 다른 콜백을 등록합니다:
before_action :external_store_check,
only: %i[_show_secure_deprecated show_secure]
Rails는 동일한 콜백의 반복 등록을 재정의(redefinition)로 취급합니다 따라서 후자가 이전의 only: 조건을 대체하는 것으로 보입니다.

그 결과, external_store_check가 더 이상 generate_presigned_put에 대해 실행되지 않아 로컬 스토어가 직접 업로드 코드 경로에 도달하게 됩니다.
재현 방법
- 로컬 업로드 스토리지를 사용하여 Discourse 인스턴스를 구성합니다.
- 다음을 확인합니다:
SiteSetting.enable_s3_uploads = false
SiteSetting.enable_direct_s3_uploads = true
- 파일을 업로드합니다.
FileStore::LocalStore에서 발생하는NoMethodError를 관찰합니다.
상태를 콘솔에서 확인하려면 다음을 사용할 수 있습니다:
[
SiteSetting.enable_direct_s3_uploads,
SiteSetting.enable_s3_uploads,
Discourse.store.class,
Discourse.store.external?
]
제안된 수정
콜백 등록이 서로를 덮어쓰지 않아야 합니다.
예를 들어, 기존 external_store_check 콜백에 보안 업로드 액션을 포함시킬 수 있습니다:
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
]
또는 보안 업로드 액션을 위해 별도의 콜백 메서드를 사용할 수도 있습니다.
S3/외부 업로드가 비활성화된 상태에서 enable_direct_s3_uploads를 활성화할 수 없도록 검증 기능을 추가하는 것도 가치가 있을 수 있습니다.
임시 해결책
로컬 업로드 스토리지를 사용하는 사이트의 경우:
SiteSetting.enable_direct_s3_uploads = false
로 설정하면 오류를 방지할 수 있습니다.