Skip to content

Commit cc88c8a

Browse files
icecrasher321claude
andcommitted
fix(file): answer null for a key a file input cannot be classified by
fileInputToUserFile normalizes caller-supplied file objects and returns null for anything it cannot use — but it classified the storage key with the throwing form, so a key without a recognized context prefix escaped as a 500 from every operation that normalizes a file input, not just write. Adds tryInferContextFromKey beside inferContextFromKey, sharing the one prefix list so a new context cannot be added to half of them. The throwing form stays right where an unclassifiable key means the platform built one wrong; the nullable form is for keys that arrived in a request, where an unrecognized prefix only means this is not a file we can use. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 43f37cf commit cc88c8a

3 files changed

Lines changed: 51 additions & 7 deletions

File tree

apps/sim/lib/internal/file/operations.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ import {
4444
import {
4545
getFileExtension,
4646
getMimeTypeFromExtension,
47-
inferContextFromKey,
47+
tryInferContextFromKey,
4848
} from '@/lib/uploads/utils/file-utils'
4949
import {
5050
downloadFileFromStorage,
@@ -159,6 +159,12 @@ const fileInputToUserFile = (fileInput: unknown) => {
159159

160160
if (!fileUrl && !key) return null
161161

162+
// A key this normalizer cannot classify is request input we cannot use, which
163+
// is what `null` already means here — the throwing form would turn a malformed
164+
// client value into a 500 from every operation that normalizes a file input.
165+
const context = key ? tryInferContextFromKey(key) : null
166+
if (key && !context) return null
167+
162168
return {
163169
id: key || fileUrl,
164170
name:
@@ -170,7 +176,9 @@ const fileInputToUserFile = (fileInput: unknown) => {
170176
? record.type.trim()
171177
: 'application/octet-stream',
172178
key,
173-
context: inferContextFromKey(key),
179+
// Only absent when there is no key at all — an unclassifiable one returned
180+
// above rather than reaching here.
181+
context: context ?? undefined,
174182
}
175183
}
176184

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { inferContextFromKey, tryInferContextFromKey } from '@/lib/uploads/utils/file-utils'
3+
4+
describe('tryInferContextFromKey', () => {
5+
it('classifies a known prefix the same way the throwing form does', () => {
6+
for (const key of ['workspace/a/b.txt', 'execution/a/b/c/d.bin', 'kb/x', 'logs/y']) {
7+
expect(tryInferContextFromKey(key)).toBe(inferContextFromKey(key))
8+
}
9+
})
10+
11+
it('answers null where the throwing form raises, so caller input cannot 500', () => {
12+
for (const key of ['', 'garbage', 'not-a-prefix/x.txt', '../escape']) {
13+
expect(tryInferContextFromKey(key)).toBeNull()
14+
expect(() => inferContextFromKey(key)).toThrow()
15+
}
16+
})
17+
})

apps/sim/lib/uploads/utils/file-utils.ts

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -757,9 +757,30 @@ export function isInternalFileUrl(fileUrl: string): boolean {
757757
* row — see `resolveStoredFileContext` — never this prefix.
758758
*/
759759
export function inferContextFromKey(key: string): StorageContext {
760-
if (!key) {
761-
throw new Error('Cannot infer context from empty key')
760+
const context = tryInferContextFromKey(key)
761+
if (!context) {
762+
throw new Error(
763+
key
764+
? `File key must start with a context prefix (kb/, knowledge-base/, chat/, copilot/, execution/, workspace/, profile-pictures/, og-images/, workspace-logos/, or logs/). Got: ${key}`
765+
: 'Cannot infer context from empty key'
766+
)
762767
}
768+
return context
769+
}
770+
771+
/**
772+
* {@link inferContextFromKey} for a key that came from a caller rather than from
773+
* our own storage, answering `null` instead of throwing.
774+
*
775+
* The throwing form is right where an unclassifiable key means the platform
776+
* built one wrong — that is a bug and should be loud. It is wrong where the key
777+
* is request input being normalized, because there an unrecognized prefix just
778+
* means "this is not a file we can use", and a throw turns a malformed request
779+
* into a 500. Both share this one list so a new context cannot be added to only
780+
* half of them.
781+
*/
782+
export function tryInferContextFromKey(key: string): StorageContext | null {
783+
if (!key) return null
763784

764785
if (key.startsWith('kb/') || key.startsWith('knowledge-base/')) return 'knowledge-base'
765786
if (key.startsWith('chat/')) return 'chat'
@@ -771,9 +792,7 @@ export function inferContextFromKey(key: string): StorageContext {
771792
if (key.startsWith('workspace-logos/')) return 'workspace-logos'
772793
if (key.startsWith('logs/')) return 'logs'
773794

774-
throw new Error(
775-
`File key must start with a context prefix (kb/, knowledge-base/, chat/, copilot/, execution/, workspace/, profile-pictures/, og-images/, workspace-logos/, or logs/). Got: ${key}`
776-
)
795+
return null
777796
}
778797

779798
/**

0 commit comments

Comments
 (0)