Skip to content

fix(file-service): restore configured S3 uploads - #264

Open
devin-ai-integration[bot] wants to merge 2 commits into
mainfrom
devin/1787687428-file-upload-s3-fix
Open

devin-ai-integration[bot] wants to merge 2 commits into
mainfrom
devin/1787687428-file-upload-s3-fix

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Aug 25, 2026

Copy link
Copy Markdown

Summary

  • Symptom / blast radius: File uploads from the affected file-service image return 500 storage_error because S3 rejects writes to a nonexistent bucket; all users of that image are affected.
  • Root cause (config/env): The image sets FILE_UPLOAD_ALWAYS_FAIL=true, so upload_file ignores S3_BUCKET and redirects every upload.
  • Fix: Remove the image-level override and add a regression test that rejects truthy FILE_UPLOAD_ALWAYS_FAIL defaults in Docker ENV instructions while preserving explicit runtime overrides.
  • Verification: cargo test (27 passed), cargo fmt --check, and cargo clippy --all-targets -- -D warnings; rebuilt the image and confirmed it contains no upload-failure env default. With Compose's explicit override removed from the test container, uploaded 1761710476959.jpeg through the browser, retrieved it through search/detail/download, and verified the LocalStack object bytes. A forced-true negative control reproduced the failure. Screen recording.

Independent incident run; compare with #263. This PR uses a smaller Dockerfile guard while covering case-insensitive instructions, quoted truthy values, legacy syntax, multi-variable instructions, continuations, and hashes inside values.

Link to Devin session: https://app.devin.ai/sessions/c26a89f42c4b456b8fdd48e61b1f73c9


Open in Devin Review

Co-Authored-By: bot_apk <apk@cognition.ai>
@devin-ai-integration
devin-ai-integration Bot requested a review from a team August 25, 2026 19:54
@devin-ai-integration

Copy link
Copy Markdown
Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

devin-ai-integration[bot]

This comment was marked as resolved.

Co-Authored-By: bot_apk <apk@cognition.ai>

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 new potential issues.

Open in Devin Review

Comment on lines 27 to 32
ENV FILE_SHARE_EVENT_ALWAYS_FAIL=true

# With uploads failing permanently (above), users would have no files to share.
# Seeding gives every user a few demo documents on their first file listing,
# so the share-notification failure is demoable alongside the upload failure.
# so the share flow is demoable on an account that has uploaded nothing yet.
# docker-compose.yml passes false, so local stacks behave like the golden app.
ENV FILE_SEED_DEMO_DOCS=true

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Sibling chaos flags remain baked in

The image still sets FILE_SHARE_EVENT_ALWAYS_FAIL=true and FILE_SEED_DEMO_DOCS=true (services/file-service/Dockerfile:27-32). The PR scopes itself to uploads, so share-failure and demo-seeding behavior persist by design.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +120 to +151
fn dockerfile_enables_upload_failure(dockerfile: &str) -> bool {
let logical_dockerfile = dockerfile.replace("\\\r\n", " ").replace("\\\n", " ");
logical_dockerfile.lines().any(|line| {
let instruction = line.trim();
let mut parts = instruction.splitn(2, char::is_whitespace);
if !parts
.next()
.is_some_and(|part| part.eq_ignore_ascii_case("ENV"))
{
return false;
}

let fields: Vec<_> = parts
.next()
.unwrap_or_default()
.split_whitespace()
.collect();
if fields.first().is_some_and(|field| field.contains('=')) {
return fields.iter().any(|field| {
field.split_once('=').is_some_and(|(name, value)| {
name == "FILE_UPLOAD_ALWAYS_FAIL"
&& parse_bool(value.trim_matches(['"', '\'']), false)
})
});
}

fields.first() == Some(&"FILE_UPLOAD_ALWAYS_FAIL")
&& fields
.get(1)
.is_some_and(|value| parse_bool(value.trim_matches(['"', '\'']), false))
})
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Regression parser is best-effort

dockerfile_enables_upload_failure models only a subset of Docker ENV syntax (backslash continuations, simple quote stripping, legacy and equals forms). It is adequate as a guard against re-adding the truthy default but does not replicate Docker's full escaping rules.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@devin-ai-integration

Copy link
Copy Markdown
Author

Runtime verification

Verified the browser upload path against the local stack with FILE_UPLOAD_ALWAYS_FAIL absent from the container environment, so the rebuilt image default—not Compose's explicit false override—was exercised.

Screen recording

Upload complete

File detail

  • Upload of 1761710476959.jpeg returned 201; file-service logged the write to otterworks-files.
  • Search and the detail route retrieved the new 162 B image/jpeg object.
  • The Download action served the object through the presigned S3 URL; LocalStack head-object returned ContentLength: 162 and an ETag matching the source file MD5.
  • Negative control: forcing FILE_UPLOAD_ALWAYS_FAIL=true reproduced the red upload failure, then removing it restored success.

A pre-existing pagination issue prevents new uploads from appearing in the seeded Files grid after the first 50 records; search and the detail route confirm this upload exists. This is outside the PR scope.

Written by Devin

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants