close
Skip to content
Prev Previous commit
Next Next commit
fix(chat): resolve uploads whose name contains a literal percent
A name like test%2A.zip is exposed double-encoded by glob/upload-context
(test%252A.zip) but canonicalUploadKey decodes the input first, so a literal
%2A is indistinguishable from an encoded * and the lookup misses. Add an
encoded-form fallback (encode the stored name, compare to the raw input) which
recovers the row without affecting the U+202F normalization path.
  • Loading branch information
waleedlatif1 committed Jun 28, 2026
commit 5ea26995d89fbe61efe652c06cafc652ce817be5
16 changes: 16 additions & 0 deletions apps/sim/lib/copilot/tools/handlers/upload-file-reader.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,22 @@ describe('findMothershipUploadRowByChatAndName', () => {

expect(result?.id).toBe('wf_3')
})

it('resolves a literal-% name via its encoded glob form', async () => {
// Stored name has a literal `%`; glob/upload-context expose it double-encoded
// (`test%252A.zip`). The encoded-form fallback recovers the row.
const row = makeRow({
id: 'wf_pct',
displayName: 'test%2A.zip',
contentType: 'application/zip',
})
mockOrderByThenLimit([])
dbChainMockFns.orderBy.mockResolvedValueOnce([row] as never)

const result = await findMothershipUploadRowByChatAndName(CHAT_ID, 'test%252A.zip')

expect(result?.id).toBe('wf_pct')
})
})

describe('listChatUploads', () => {
Expand Down
25 changes: 24 additions & 1 deletion apps/sim/lib/copilot/tools/handlers/upload-file-reader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,21 @@ function canonicalUploadKey(name: string): string {
}
}

/**
* Per-segment encode of a stored name (no decode first), so a name containing a
* literal `%` (e.g. `test%2A.zip`) round-trips: glob/upload-context expose it as
* `encodeVfsSegment(name)`, and matching that encoded form back recovers the row.
* {@link canonicalUploadKey} can't, because it decodes the input first and a
* literal `%2A` is indistinguishable from an encoded `*`.
*/
function encodeUploadName(name: string): string {
try {
return encodeVfsSegment(name)
} catch {
return name.trim()
}
}

/** VFS-visible name. Coalesces to originalName for legacy rows that predate displayName. */
function vfsName(row: typeof workspaceFiles.$inferSelect): string {
return row.displayName ?? row.originalName
Expand Down Expand Up @@ -127,7 +142,15 @@ export async function findMothershipUploadRowByChatAndName(
.orderBy(desc(workspaceFiles.uploadedAt), desc(workspaceFiles.id))

const segmentKey = canonicalUploadKey(fileName)
return allRows.find((r) => canonicalUploadKey(vfsName(r)) === segmentKey) ?? null
return (
allRows.find((r) => {
const stored = vfsName(r)
// Canonical-key match handles visually-equivalent spellings (U+202F vs
// space); the encoded-form match handles literal `%` names that survive
// encode but not decode.
return canonicalUploadKey(stored) === segmentKey || encodeUploadName(stored) === fileName
}) ?? null
)
}

/**
Expand Down
Loading