fix: allow larger binary attachments (images/png)#154
Conversation
Binary attachments like images are not sent to LLM as content - only the filename is included in the prompt context. So there's no need to enforce the 256KB limit that applies to text attachments. - Add separate MAX_BINARY_ATTACHMENT_BYTES (10MB) limit - Only read full file for text attachments, probe-only for binary - Fixes "Attachment ... is too large" error when uploading images Co-Authored-By: Claude Opus 4.7 <[email protected]>
There was a problem hiding this comment.
Findings
- [Major] Unknown-extension text files can bypass size enforcement and get silently truncated — this violates the no-silent-fallback constraint and can hide prompt-context loss from users, evidence
apps/desktop/src/main/prompt-context.ts:67andapps/desktop/src/main/prompt-context.ts:99
Suggested fix:const looksText = isProbablyText(probe, extension); if (looksText && file.size > MAX_TEXT_ATTACHMENT_BYTES) { throw new CodesignError( `Text attachment "${file.name}" is too large (${file.size} bytes). Maximum is ${MAX_TEXT_ATTACHMENT_BYTES} bytes.`, ERROR_CODES.ATTACHMENT_TOO_LARGE, ); } if (!looksText) { buffer = probe; } else { const length = Math.max(1, Math.min(file.size || MAX_TEXT_ATTACHMENT_BYTES, MAX_TEXT_ATTACHMENT_BYTES)); const fullBuffer = Buffer.alloc(length); const { bytesRead } = await handle.read(fullBuffer, 0, fullBuffer.length, 0); buffer = fullBuffer.subarray(0, bytesRead); }
Summary
Review mode: initial
One issue found in the latest diff: unknown text-like attachments with non-text extensions are accepted up to 10MB and then truncated to 256KB without raising an error (apps/desktop/src/main/prompt-context.ts:99). docs/VISION.md and docs/PRINCIPLES.md were not available in this checkout (Not found in repo/docs), so assessment used repo-available constraints (CLAUDE.md).
Testing
Not run (automation). Suggested coverage: add Vitest cases for readAttachment that verify (1) unknown-extension text file >256KB throws ATTACHMENT_TOO_LARGE, and (2) unknown-extension binary file <=10MB returns binary note without content.
open-codesign Bot
| const { bytesRead } = await handle.read(fullBuffer, 0, fullBuffer.length, 0); | ||
| buffer = fullBuffer.subarray(0, bytesRead); | ||
| } else { | ||
| // Looks like text but exceeds text limit - this can only happen for unknown extensions |
There was a problem hiding this comment.
[Major] This branch silently truncates unknown-extension files that look like text to MAX_TEXT_ATTACHMENT_BYTES instead of throwing, so oversized text input can bypass UI-visible size errors. Please throw ATTACHMENT_TOO_LARGE when isProbablyText(probe, extension) is true and file.size > MAX_TEXT_ATTACHMENT_BYTES.
Signed-off-by: Sun-sunshine06 <[email protected]>
There was a problem hiding this comment.
Findings
- [Major] Unknown-extension text attachments can still bypass the text-size error via silent truncation — this can hide prompt-context loss and violates the no-silent-fallback constraint, evidence
apps/desktop/src/main/prompt-context.ts:103
Suggested fix:
const looksText = isProbablyText(probe, extension);
if (looksText && file.size > MAX_TEXT_ATTACHMENT_BYTES) {
throw new CodesignError(
`Text attachment "${file.name}" is too large (${file.size} bytes). Maximum is ${MAX_TEXT_ATTACHMENT_BYTES} bytes.`,
ERROR_CODES.ATTACHMENT_TOO_LARGE,
);
}
if (!looksText) {
buffer = probe;
} else {
const length = Math.max(1, Math.min(file.size || MAX_TEXT_ATTACHMENT_BYTES, MAX_TEXT_ATTACHMENT_BYTES));
const fullBuffer = Buffer.alloc(length);
const { bytesRead } = await handle.read(fullBuffer, 0, fullBuffer.length, 0);
buffer = fullBuffer.subarray(0, bytesRead);
}Summary
Review mode: follow-up after new commits
One issue remains in the latest PR diff: the else branch still truncates unknown-extension files that look like text instead of surfacing ATTACHMENT_TOO_LARGE (apps/desktop/src/main/prompt-context.ts:103). docs/VISION.md and docs/PRINCIPLES.md are not present in this checkout (Not found in repo/docs).
Testing
Not run (automation). Suggested coverage: add a Vitest case where an unknown-extension text file over MAX_TEXT_ATTACHMENT_BYTES throws ATTACHMENT_TOO_LARGE.
open-codesign Bot
| // Read from start (we already have the probe, but just re-read for simplicity) | ||
| const { bytesRead } = await handle.read(fullBuffer, 0, fullBuffer.length, 0); | ||
| buffer = fullBuffer.subarray(0, bytesRead); | ||
| } else { |
There was a problem hiding this comment.
[Major] This branch silently truncates unknown-extension files that look like text instead of surfacing ATTACHMENT_TOO_LARGE, which violates the no-silent-fallback constraint. Please throw when isProbablyText(probe, extension) is true and file.size > MAX_TEXT_ATTACHMENT_BYTES.
There was a problem hiding this comment.
Findings
- [Major] Unknown-extension text attachments are silently truncated instead of failing fast — this hides prompt-context loss and violates the no-silent-fallback constraint, evidence
apps/desktop/src/main/prompt-context.ts:103
Suggested fix:
const looksText = isProbablyText(probe, extension);
if (looksText && file.size > MAX_TEXT_ATTACHMENT_BYTES) {
throw new CodesignError(
`Text attachment "${file.name}" is too large (${file.size} bytes). Maximum is ${MAX_TEXT_ATTACHMENT_BYTES} bytes.`,
ERROR_CODES.ATTACHMENT_TOO_LARGE,
);
}
buffer = looksText
? (await handle.read(Buffer.alloc(Math.max(1, Math.min(file.size || MAX_TEXT_ATTACHMENT_BYTES, MAX_TEXT_ATTACHMENT_BYTES))), 0, Math.max(1, Math.min(file.size || MAX_TEXT_ATTACHMENT_BYTES, MAX_TEXT_ATTACHMENT_BYTES)), 0)).buffer
: probe;Summary
Review mode: follow-up after new commits
One issue remains in the latest diff: the new else branch for files that look like text but exceed MAX_TEXT_ATTACHMENT_BYTES truncates content instead of throwing ATTACHMENT_TOO_LARGE (apps/desktop/src/main/prompt-context.ts:103).
Testing
Not run (automation). Suggested tests: add a Vitest case where an unknown-extension text file over MAX_TEXT_ATTACHMENT_BYTES throws ATTACHMENT_TOO_LARGE, and a case where a binary file under MAX_BINARY_ATTACHMENT_BYTES returns a filename-only note.
open-codesign Bot
| // Read from start (we already have the probe, but just re-read for simplicity) | ||
| const { bytesRead } = await handle.read(fullBuffer, 0, fullBuffer.length, 0); | ||
| buffer = fullBuffer.subarray(0, bytesRead); | ||
| } else { |
There was a problem hiding this comment.
[Major] This branch silently truncates files that probe as text when file.size > MAX_TEXT_ATTACHMENT_BYTES. Please throw ATTACHMENT_TOO_LARGE here to avoid hidden prompt-context loss and satisfy the no-silent-fallback constraint.
…tension Signed-off-by: Sun-sunshine06 <[email protected]>
There was a problem hiding this comment.
Findings
- [Major]
ATTACHMENT_TOO_LARGEis remapped toATTACHMENT_READ_FAILEDfor text-like oversized files — this hides the real validation failure and can break UI handling that depends on the correct error code, evidenceapps/desktop/src/main/prompt-context.ts:91andapps/desktop/src/main/prompt-context.ts:113.
Suggested fix:
} catch (error) {
if (error instanceof CodesignError) {
throw error;
}
throw new CodesignError(
`Failed to read attachment "${file.path}"`,
ERROR_CODES.ATTACHMENT_READ_FAILED,
{
cause: error,
},
);
}Summary
Review mode: follow-up after new commits
One major issue found in the newly added oversized-text guard: the thrown CodesignError is currently caught and wrapped as ATTACHMENT_READ_FAILED.
open-codesign Bot
Testing
Not run (automation). Missing/updated Vitest coverage for the new probe path is not included in this diff (especially oversized unknown-extension text should assert ATTACHMENT_TOO_LARGE).
| const probe = probeBuffer.subarray(0, probeRead); | ||
|
|
||
| const looksText = isProbablyText(probe, extension); | ||
| if (looksText && file.size > MAX_TEXT_ATTACHMENT_BYTES) { |
There was a problem hiding this comment.
[Major] The new oversized-text thrown in this branch is currently caught by the broad below and remapped to . Please rethrow existing instances so surfaces unchanged.
There was a problem hiding this comment.
Findings
- [Major]
ATTACHMENT_TOO_LARGEis still remapped toATTACHMENT_READ_FAILEDin the new text-probe guard path, which hides validation semantics and can break UI error handling keyed on error code, evidenceapps/desktop/src/main/prompt-context.ts:91andapps/desktop/src/main/prompt-context.ts:113.
Suggested fix:
} catch (error) {
if (error instanceof CodesignError) {
throw error;
}
throw new CodesignError(
`Failed to read attachment "${file.path}"`,
ERROR_CODES.ATTACHMENT_READ_FAILED,
{
cause: error,
},
);
}Summary
- Review mode: follow-up after new commits
- 1 major issue remains in the latest diff; no additional high-confidence issues found on added/modified lines.
- Context docs check:
docs/VISION.mdanddocs/PRINCIPLES.mdwere not available in this checkout (Not found in repo/docs).
Testing
- Not run (automation)
open-codesign Bot
| const { bytesRead } = await handle.read(fullBuffer, 0, fullBuffer.length, 0); | ||
| buffer = fullBuffer.subarray(0, bytesRead); | ||
| } | ||
| } catch (error) { |
There was a problem hiding this comment.
[Major] A CodesignError thrown in this block (including the new oversized-text guard) is still caught and remapped to ATTACHMENT_READ_FAILED. Please rethrow existing CodesignError instances so ATTACHMENT_TOO_LARGE surfaces unchanged.
…_TOO_LARGE code Signed-off-by: Sun-sunshine06 <[email protected]>
There was a problem hiding this comment.
Findings
- None.
Summary
- Review mode: follow-up after new commits
- No new high-confidence issues found on added/modified lines in the latest diff.
- Follow-up check: prior
CodesignErrorremap issue is resolved inapps/desktop/src/main/prompt-context.tsvia explicit rethrow. - Residual risk/testing gap: behavior changes for text-vs-binary size limits and probe-based classification do not include accompanying Vitest coverage in this PR.
- Context docs:
docs/VISION.mdanddocs/PRINCIPLES.mdare not available in this checkout (docs/is gitignored), so internal-doc conformance could not be re-validated here (Not found in repo/docs).
Testing
- Not run (automation)
open-codesign Bot
…n text Signed-off-by: Sun-sunshine06 <[email protected]>
There was a problem hiding this comment.
Findings
- [Major] Unknown-extension size-limit test is invalid because the fixture file does not exist, so the assertion cannot verify
ATTACHMENT_TOO_LARGEand instead triggers read-failure behavior; this can produce false confidence or CI failure depending on matcher behavior. Evidenceapps/desktop/src/main/prompt-context.test.ts:48,apps/desktop/src/main/prompt-context.test.ts:52
Suggested fix:it('throws ATTACHMENT_TOO_LARGE for unknown-extension text > 256KB', async () => { const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'prompt-context-')); const filePath = path.join(dir, 'data.bin'); await fs.writeFile(filePath, 'a'.repeat(300_000), 'utf8'); await expect( preparePromptContext({ attachments: [{ path: filePath, name: 'data.bin', size: 300_000 }], }), ).rejects.toMatchObject({ name: 'CodesignError', code: 'ATTACHMENT_TOO_LARGE', }); });
Summary
- Review mode: follow-up after new commits
- 1 issue found on added/modified lines in the latest diff.
- Constraint checks in changed lines: no direct provider SDK import, no silent fallback introduced, no new dependency/license change detected.
- Context docs
docs/VISION.mdanddocs/PRINCIPLES.md: Not found in repo/docs.
Testing
- Not run (automation)
open-codesign Bot
| // Unknown extension but it's actually text - should still throw based on content probe | ||
| const err = await expect( | ||
| preparePromptContext({ | ||
| attachments: [{ path: 'C:/repo/data.bin', name: 'data.bin', size: 300_000 }], |
There was a problem hiding this comment.
This test input uses a non-existent file (C:/repo/data.bin), so preparePromptContext fails at file open and cannot validate the intended unknown-extension text size-limit path. Please create a real temp file with >256KB UTF-8 text and assert rejects.toMatchObject({ code: "ATTACHMENT_TOO_LARGE" }) directly.
Signed-off-by: Sun-sunshine06 <[email protected]>
There was a problem hiding this comment.
Findings
- [Major] Unknown-extension oversize test never reaches the text-size branch because it uses a non-existent fixture path, so it validates read-failure behavior instead of
ATTACHMENT_TOO_LARGE. This can hide regressions in the probe-based size enforcement logic. Evidenceapps/desktop/src/main/prompt-context.test.ts:48
Suggested fix:it('throws ATTACHMENT_TOO_LARGE for unknown-extension text > 256KB', async () => { const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'prompt-context-')); const filePath = path.join(dir, 'data.bin'); await fs.writeFile(filePath, 'a'.repeat(300_000), 'utf8'); await expect( preparePromptContext({ attachments: [{ path: filePath, name: 'data.bin', size: 300_000 }], }), ).rejects.toMatchObject({ name: 'CodesignError', code: 'ATTACHMENT_TOO_LARGE', }); });
Summary
- Review mode: follow-up after new commits
- 1 issue found on added/modified lines in the latest diff.
- Constraint checks in changed lines: no direct provider SDK import, no silent fallback introduced, no new dependency/license change detected.
- Required docs
docs/VISION.mdanddocs/PRINCIPLES.md: Not found in repo/docs.
Testing
- Not run (automation)
open-codesign Bot
|
|
||
| it('throws ATTACHMENT_TOO_LARGE for unknown extension text > 256KB', async () => { | ||
| // Unknown extension but it's actually text - should still throw based on content probe | ||
| const err = await expect( |
There was a problem hiding this comment.
This assertion path uses a file that does not exist (C:/repo/data.bin), so the test can fail for read reasons before validating the intended ATTACHMENT_TOO_LARGE behavior for unknown-extension text. Please create a real temp file in-test and assert the size error code explicitly.
Suggested fix:
it('throws ATTACHMENT_TOO_LARGE for unknown-extension text > 256KB', async () => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'prompt-context-'));
const filePath = path.join(dir, 'data.bin');
await fs.writeFile(filePath, 'a'.repeat(300_000), 'utf8');
await expect(
preparePromptContext({
attachments: [{ path: filePath, name: 'data.bin', size: 300_000 }],
}),
).rejects.toMatchObject({
name: 'CodesignError',
code: 'ATTACHMENT_TOO_LARGE',
});
});There was a problem hiding this comment.
Findings
- None. No high-confidence issues found on added/modified lines in the latest diff.
Summary
- Review mode: follow-up after new commits
- No blocking/major/minor/nit issues identified in changed lines.
- Constraint checks in changed lines: no direct provider SDK imports, no new silent fallback behavior, no dependency/license/install-size deltas in this diff.
- Required docs
docs/VISION.mdanddocs/PRINCIPLES.md: Not found in repo/docs.
Testing
- Not run (automation).
- Suggested additional coverage: add one test for binary attachment >10MB returning
ATTACHMENT_TOO_LARGE, and one test with an existing small binary file asserting it is treated as binary context (filename-only note path).
open-codesign Bot
Binary attachments like images are not sent to LLM as content - only the filename is included in the prompt context. So there's no need to enforce the 256KB limit that applies to text attachments.
Summary
Binary attachments (images, etc.) are only referenced by filename in the prompt context, never read as content. The previous 256KB cap was incorrectly applied to them, causing spurious "too large" errors. This PR introduces a separate 10MB limit for binary files and restricts full reads to text attachments only.
Type of change
Linked issue
Checklist
docs/VISION.md,docs/PRINCIPLES.md, andCLAUDE.mdbefore startinggit commit -s)pnpm lint && pnpm typecheck && pnpm testpasses locally (CI verifies)pnpm changeset) if user-visibleDependency additions (if any)
None.
Screenshots / recordings (UI changes)
N/A — no visual changes.