Skip to content

fix(host-service): accept unknown mediaType on attachment upload#4439

Merged
saddlepaddle merged 1 commit into
mainfrom
fix/create-modal-mediatype-validation
May 12, 2026
Merged

fix(host-service): accept unknown mediaType on attachment upload#4439
saddlepaddle merged 1 commit into
mainfrom
fix/create-modal-mediatype-validation

Conversation

@saddlepaddle
Copy link
Copy Markdown
Collaborator

@saddlepaddle saddlepaddle commented May 12, 2026

Summary

  • Users uploading files with custom extensions (e.g. a .bmad Fortran lattice file for accelerator simulations) hit Too small: expected string to have >=1 characters on mediaType because browsers report empty File.type for unknown extensions.
  • Even with a non-empty mediaType, the upload handler then threw Unrecognized media type for anything mime-types didn't know.
  • Now the upload handler falls back to application/octet-stream when the mediaType is empty or unknown. Original filename is still preserved in metadata, and the agent receives the file via its on-disk path (with .bin extension) plus the original filename.

Test plan

  • Existing attachment tests still pass (14/14)
  • New test: unknown mediaType (application/x-totally-fake) is accepted and stored as .bin
  • New test: empty mediaType is accepted and stored as octet-stream
  • Typecheck + biome lint clean
  • Manual: drop /tmp/sample.bmad into the new-workspace modal and confirm it uploads

Summary by cubic

Fixes attachment uploads failing for unknown file types by accepting empty or unrecognized mediaType so custom extensions (e.g. .bmad) upload successfully.

  • Bug Fixes
    • Relaxed attachments.upload input to allow empty mediaType.
    • If mime-types can’t resolve the type, save as application/octet-stream with a .bin path and keep the original filename in metadata; added tests for unknown and empty mediaType.

Written for commit d9fcadb. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • File uploads with unrecognized media types are now accepted and automatically handled with a fallback format instead of being rejected.
  • Bug Fixes

    • Improved handling of edge cases in file upload validation to support broader file type compatibility.

Review Change Stack

Browsers report empty File.type for unknown extensions (e.g. custom
simulator extensions like .bmad), which was bouncing real uploads at
the schema and at the mimeTypes.extension check. Fall back to
application/octet-stream so the file lands; original filename is still
preserved in metadata.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 12, 2026

📝 Walkthrough

Walkthrough

The attachment upload handler changes from rejecting unrecognized media types to accepting them and falling back to application/octet-stream. The input schema is relaxed to accept any media type string, a fallback constant is introduced, and the upload logic applies the fallback when the type cannot be resolved. Tests are updated to verify the new behavior.

Changes

Attachment Media Type Fallback

Layer / File(s) Summary
Input schema and fallback constant
packages/host-service/src/trpc/router/attachments/attachments.ts
The mediaType input field is updated to accept plain strings without length validation. A FALLBACK_MEDIA_TYPE constant is added for use when the provided media type is unrecognized.
Upload mutation media type resolution
packages/host-service/src/trpc/router/attachments/attachments.ts
The upload mutation resolves input.mediaType to a supported extension type or falls back to the constant. The attachment metadata is constructed using the resolved or fallback value instead of the raw input.
Test verification of fallback behavior
packages/host-service/src/trpc/router/attachments/attachments.test.ts
Tests now verify that unrecognized and empty mediaType values fall back to application/octet-stream and that the stored attachment file uses the .bin extension.

🎯 2 (Simple) | ⏱️ ~10 minutes

🐰 No more media-type rejection here,
Just graceful fallback, loud and clear.
When types are strange or left alone,
octet-stream becomes the home. 🎒

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely summarizes the primary change: accepting unknown mediaTypes on attachment uploads rather than rejecting them.
Description check ✅ Passed The description is comprehensive and addresses all critical sections: it explains the problem, the solution, and testing coverage. It follows the template structure with clear summary and test plan sections.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/create-modal-mediatype-validation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 12, 2026

Greptile Summary

This PR fixes the upload handler to gracefully accept files whose MIME type is empty or unrecognised by mime-types, falling back to application/octet-stream (.bin on disk) instead of throwing a BAD_REQUEST error. The Zod schema is loosened from z.string().min(1) to z.string() to allow the empty string that browsers emit for unknown extensions, and the hard rejection of unknown media types is replaced with the fallback constant.

  • attachments.ts: removes the TRPCError throw for unknown MIME types and substitutes a ternary fallback; the resolved mediaType (not the raw input) is written to metadata and returned to the caller.
  • attachments.test.ts: the old "rejects unrecognized media type" test is converted into two new tests covering the unknown and empty-string fallback paths; the unknown-type test fully verifies the on-disk .bin file, while the empty-type test only checks the returned mediaType field.

Confidence Score: 4/5

Safe to merge — the production change is a small, well-scoped fallback with no effect on existing known MIME types.

The implementation change is minimal and correct: mimeTypes.extension is falsy for both empty strings and unrecognised types, so the fallback engages exactly where intended. The only gap is that the empty-mediaType test skips verifying the on-disk write and metadata, so a future regression in writeAttachment on that path would be silently missed by that test.

The empty-mediaType test in attachments.test.ts is lighter than its unrecognised-type sibling and would benefit from an existsSync assertion.

Important Files Changed

Filename Overview
packages/host-service/src/trpc/router/attachments/attachments.ts Relaxes mediaType schema from min(1) to allow empty string, and replaces the hard rejection of unknown MIME types with a fallback to application/octet-stream. Logic is correct and the fallback constant is clean.
packages/host-service/src/trpc/router/attachments/attachments.test.ts Converts the old rejection test into two new fallback tests. The unrecognized media type test fully verifies disk writes; the empty media type test only asserts the returned mediaType without checking that the file was written or that originalFilename is persisted to metadata.json.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Client calls upload] --> B{Zod validation}
    B -- fails --> ERR1[TRPC validation error]
    B -- passes --> C{mimeTypes.extension check}
    C -- extension found --> D[Use input.mediaType as-is]
    C -- false or empty string --> E[Fallback to application/octet-stream]
    D --> F{Size estimate}
    E --> F
    F -- oversized --> ERR2[PAYLOAD_TOO_LARGE]
    F -- ok --> G[Buffer.from base64 decode]
    G -- 0 bytes --> ERR3[BAD_REQUEST empty]
    G -- bytes ok --> H[writeAttachment with resolved mediaType]
    H --> I[Return attachmentId + mediaType + originalFilename + sizeBytes]
Loading
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
packages/host-service/src/trpc/router/attachments/attachments.test.ts:103-111
**Empty-mediaType test doesn't verify disk write or metadata**

The unrecognized-mediaType test (line 87) fully verifies the upload by checking `existsSync(filePath)` and that the path ends in `.bin`. This test only checks `result.mediaType`, leaving it possible that a regression in `writeAttachment` (e.g. an exception thrown after the fallback is selected) would go undetected here. Adding an `existsSync` assertion and a check that `metadata.json` contains `originalFilename: "racetrack.bmad"` would give symmetrical coverage to the two new fallback paths.

Reviews (1): Last reviewed commit: "fix(host-service): accept unknown mediaT..." | Re-trigger Greptile

Comment on lines +103 to 111
it("falls back to application/octet-stream for empty media type", async () => {
const caller = createCaller();
const result = await caller.upload({
data: { kind: "base64", data: PNG_BASE64 },
mediaType: "",
originalFilename: "racetrack.bmad",
});
expect(result.mediaType).toBe("application/octet-stream");
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Empty-mediaType test doesn't verify disk write or metadata

The unrecognized-mediaType test (line 87) fully verifies the upload by checking existsSync(filePath) and that the path ends in .bin. This test only checks result.mediaType, leaving it possible that a regression in writeAttachment (e.g. an exception thrown after the fallback is selected) would go undetected here. Adding an existsSync assertion and a check that metadata.json contains originalFilename: "racetrack.bmad" would give symmetrical coverage to the two new fallback paths.

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/host-service/src/trpc/router/attachments/attachments.test.ts
Line: 103-111

Comment:
**Empty-mediaType test doesn't verify disk write or metadata**

The unrecognized-mediaType test (line 87) fully verifies the upload by checking `existsSync(filePath)` and that the path ends in `.bin`. This test only checks `result.mediaType`, leaving it possible that a regression in `writeAttachment` (e.g. an exception thrown after the fallback is selected) would go undetected here. Adding an `existsSync` assertion and a check that `metadata.json` contains `originalFilename: "racetrack.bmad"` would give symmetrical coverage to the two new fallback paths.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/host-service/src/trpc/router/attachments/attachments.test.ts (1)

103-111: ⚡ Quick win

Strengthen the empty-mediaType test with persistence assertions.

This test currently verifies only the returned mediaType. Add .bin path and existsSync checks (same as Line 95-100) to guard storage-path regressions for empty media types too.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/host-service/src/trpc/router/attachments/attachments.test.ts` around
lines 103 - 111, The test "falls back to application/octet-stream for empty
media type" only asserts result.mediaType; extend it to also assert the
persisted filename and file existence like the other test: after calling
caller.upload (using PNG_BASE64), assert that result.path ends with ".bin" (or
that the stored filename has a .bin extension) and use
fs.existsSync(path.join(attachments storage dir, result.path)) to verify the
file was actually written; reference the test's caller.upload call, result.path,
PNG_BASE64, and use existsSync/path.join to perform the additional checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/host-service/src/trpc/router/attachments/attachments.test.ts`:
- Around line 103-111: The test "falls back to application/octet-stream for
empty media type" only asserts result.mediaType; extend it to also assert the
persisted filename and file existence like the other test: after calling
caller.upload (using PNG_BASE64), assert that result.path ends with ".bin" (or
that the stored filename has a .bin extension) and use
fs.existsSync(path.join(attachments storage dir, result.path)) to verify the
file was actually written; reference the test's caller.upload call, result.path,
PNG_BASE64, and use existsSync/path.join to perform the additional checks.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 32bbf230-a66a-4a55-9b80-a303a308e52a

📥 Commits

Reviewing files that changed from the base of the PR and between 9d7e1b2 and d9fcadb.

📒 Files selected for processing (2)
  • packages/host-service/src/trpc/router/attachments/attachments.test.ts
  • packages/host-service/src/trpc/router/attachments/attachments.ts

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

No issues found across 2 files

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 12, 2026

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ✅ Neon database branch

Thank you for your contribution! 🎉

@saddlepaddle saddlepaddle merged commit a76215e into main May 12, 2026
17 checks passed
@saddlepaddle saddlepaddle mentioned this pull request May 12, 2026
3 tasks
saddlepaddle added a commit that referenced this pull request May 12, 2026
Changes since v0.2.14:

- workspaces: `superset workspaces list` now accepts `--project` and
  `--search` filters, matching the desktop list view. (#4455)
- cli-framework: `--help` on a subcommand now shows the global options
  (e.g. `--json`, `--quiet`, `--api-key`) instead of hiding them. (#4424)
- host-service: attachment upload no longer rejects unknown mediaType
  values returned by some hosts. (#4439)
- host-service: PR fetch is now per-branch, avoiding 504s on repos with
  large numbers of open PRs. (#4268)

Push cli-v0.2.15 after this lands to fire the release pipeline.
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.

1 participant