Skip to content

feat(coderd/x/chatd): agent-created file attachments in chat#24280

Merged
ethanndickson merged 1 commit intomainfrom
ethan/chat-file-attachments
Apr 20, 2026
Merged

feat(coderd/x/chatd): agent-created file attachments in chat#24280
ethanndickson merged 1 commit intomainfrom
ethan/chat-file-attachments

Conversation

@ethanndickson
Copy link
Copy Markdown
Member

@ethanndickson ethanndickson commented Apr 13, 2026

Agents can already see workspace files and take screenshots, but users could not download those artifacts from chat. This PR adds durable chat attachments to chatd. attach_file, explicit computer screenshot actions (not the automatic post-action screenshots), and propose_plan now fetch bytes over the agent connection, store them in chat_files, link them to the chat, and carry attachment metadata in tool responses so buildAssistantPartsForPersist can materialize ordinary type:"file" assistant parts that the chat file APIs serve.

The same storage helpers are reused for other artifact-producing paths. wait_agent recordings and thumbnails are stored as chat files and linked back to the parent chat, with best-effort relinking so parent chats retain those artifacts without leaving orphaned rows when chat-file caps reject links. storeChatAttachment wraps insert + link in one transaction, files are capped at 10 MB each and 20 per chat, and serving defaults to Content-Disposition: attachment with an explicit inline-safe allowlist.

This PR also consolidates chat-file media policy in coderd/chatfiles. Uploads and tool-generated attachments share byte-based MIME detection, SVG blocking, inline-safety rules, and compatible text/plain refinement for JSON, CSV, and Markdown. Prompt construction still only inlines synthetic pasted text for model consumption; assistant-created attachments are persisted for the user and intentionally not replayed into later LLM turns.

UI follow-up lives in #24281.

Relates to CODAGT-91

Copy link
Copy Markdown
Member Author

ethanndickson commented Apr 13, 2026

@ethanndickson ethanndickson changed the title feat(coderd): agent-created file attachments in chat feat(coderd/x/chatd): agent-created file attachments in chat Apr 13, 2026
@ethanndickson ethanndickson marked this pull request as ready for review April 13, 2026 05:38
@coder-tasks
Copy link
Copy Markdown
Contributor

coder-tasks Bot commented Apr 13, 2026

Documentation Check

Updates Needed

  • docs/ai-coder/agents/architecture.md — The new attach_file tool should be added to the Workspace tools table. It attaches a workspace file to the current chat so the user can download it directly from the conversation (artifacts such as screenshots, logs, patches, or documents). It requires a workspace connection and an absolute file path.

    ⚠️ Still unaddressed — no documentation changes found in this PR (verified against latest commits)


Automated review via Coder Tasks

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ba37790e60

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread coderd/x/chatd/chattool/computeruse.go Outdated
@ethanndickson
Copy link
Copy Markdown
Member Author

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f84c3c3a74

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread coderd/x/chatd/chattool/attachment.go Outdated
@ethanndickson
Copy link
Copy Markdown
Member Author

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b181308ae0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread coderd/exp_chats.go Outdated
@ethanndickson ethanndickson force-pushed the ethan/chat-file-attachments branch from b181308 to 09619fc Compare April 13, 2026 14:24
@ethanndickson
Copy link
Copy Markdown
Member Author

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 09619fc6d6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread coderd/chatfiles/mime.go
@ethanndickson ethanndickson force-pushed the ethan/chat-file-attachments branch from 09619fc to f2f766d Compare April 14, 2026 07:38
@ethanndickson
Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Hooray!

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ethanndickson ethanndickson force-pushed the ethan/chat-file-attachments branch from 2429f26 to 0c202b2 Compare April 14, 2026 13:41
@ethanndickson
Copy link
Copy Markdown
Member Author

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0c202b25ad

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread coderd/exp_chats.go Outdated
@ethanndickson ethanndickson force-pushed the ethan/chat-file-attachments branch from 0c202b2 to b865dc9 Compare April 15, 2026 05:32
@ethanndickson
Copy link
Copy Markdown
Member Author

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

return toolResponse(map[string]any{
"ok": true,
"path": requestedPath,

P2 Badge Attach propose_plan output via response metadata

buildAssistantPartsForPersist only promotes attachments from ToolResultContent.ClientMetadata (AttachmentPartsFromMetadata), but propose_plan returns a plain JSON body and never calls WithAttachments. When an agent runs propose_plan, the stored file ID is not converted into a persisted type:"file" assistant part, so the plan artifact is missing from the new attachment pipeline even though the file was stored successfully.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ethanndickson
Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🎉

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link
Copy Markdown
Member

@mafredri mafredri left a comment

Choose a reason for hiding this comment

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

Solid plumbing for durable chat attachments. The storage transaction pattern in storeChatAttachment (insert + link in one tx) is the right shape, and the MIME classification pipeline is well-tested with a ~1.2:1 test-to-code ratio. The SVG-blocking intent is good. The subagent artifact isolation tests (TestWaitAgentDoesNotRelay*) prove a real security property.

3 P2, 7 P3, 2 Nit, 2 Note. The P2s are: (1) HasSVGRootElement false-positives on any text file mentioning <svg>, (2) unbounded io.ReadAll on agent response in storeWorkspaceAttachment, and (3) recording files orphaned when parent chat linking fails.

Convergent finding: four reviewers independently flagged propose_plan bypassing ClassifyStoredMediaType. Note the interaction with F4: routing propose_plan through ClassifyStoredMediaType today would trigger the same false-positive SVG blocking on legitimate Markdown plans that mention SVG. Fix F4 first, then F7 becomes safe to unify.

"A file at a .md path whose content begins with <svg> would be stored as text/markdown without triggering HasSVGRootElement. The nosniff header prevents browser reinterpretation, so there is no exploitable XSS. But the promise 'SVG content is blocked before storage' does not hold on this path." (Mafuuu, on the perimeter inconsistency)

🤖 This review was automatically generated with Coder Agents.

Comment thread coderd/chatfiles/mime.go Outdated
Comment thread coderd/x/chatd/chattool/attachment.go
Comment thread coderd/x/chatd/subagent.go Outdated
Comment thread coderd/x/chatd/chattool/proposeplan.go Outdated
Comment thread coderd/x/chatd/attachments.go Outdated
Comment thread coderd/chatfiles/mime_test.go Outdated
Comment thread coderd/chatfiles/mime.go Outdated
Comment thread coderd/x/chatd/chattool/attachment.go Outdated
Comment thread coderd/chatfiles/mime.go
Comment thread coderd/x/chatd/chattool/attachment.go Outdated
Copy link
Copy Markdown
Member

@johnstcn johnstcn left a comment

Choose a reason for hiding this comment

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

🤖

Solid extraction of MIME policy into coderd/chatfiles. The storeChatAttachment transaction pattern is the right shape, the defense-in-depth layering on uploads is well done, and the assistant-attachment-not-replayed-to-LLM design is correct.

2 P2, 1 P3, 1 Nit across 4 inline comments. The P2s were independently validated with test cases.

Comment thread coderd/chatfiles/mime.go
Comment thread coderd/x/chatd/subagent.go
Comment thread coderd/chatfiles/mime.go Outdated
Comment thread coderd/chatfiles/mime_test.go
@ethanndickson ethanndickson force-pushed the ethan/chat-file-attachments branch from 11095b3 to 6c530e8 Compare April 15, 2026 11:24
Copy link
Copy Markdown
Member

@mafredri mafredri left a comment

Choose a reason for hiding this comment

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

Round 2. All 17 R1 findings verified fixed by the panel (6 reviewers, unanimous). The fixes are thorough: the SVG parser now rejects non-whitespace CharData before the root element, io.LimitReader caps the agent response, recording artifacts use transactional insert+link, propose_plan routes through classification, PDFs are download-only, and the duplicate maps are consolidated. The regression tests are substantive.

5 new P3, 1 Nit, 1 Note. The primary convergent finding: storeRecordingArtifact is the sole remaining path that bypasses the chatfiles classification pipeline this PR establishes. Four reviewers flagged it independently.

"Two trust tiers for the same chat_files table: user uploads are byte-verified and allowlist-gated; recording artifacts trust the caller's label entirely." (Hisoka, on the recording bypass)

🤖 This review was automatically generated with Coder Agents.

Comment thread coderd/x/chatd/recording.go
Comment thread coderd/x/chatd/chattool/computeruse_test.go
Comment thread coderd/x/chatd/chattool/attachfile_test.go
Comment thread coderd/x/chatd/chattool/computeruse.go Outdated
Comment thread coderd/x/chatd/chattool/attachment.go
Comment thread coderd/x/chatd/recording.go Outdated
Comment thread coderd/chatfiles/mime.go
@ethanndickson ethanndickson requested a review from mafredri April 15, 2026 14:46
Copy link
Copy Markdown
Member

@mafredri mafredri left a comment

Choose a reason for hiding this comment

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

Round 3. All R2 fixes verified by the panel (4 reviewers). The recording classification fix (PrepareRecordingArtifact with byte-level type verification), screenshot streaming decoder, and test hardening all landed correctly.

F29 (per-entry attachment rejection) closed by panel vote (4/4 accept). All four reviewers verified that current producers emit exactly one attachment per tool result, and the per-result degradation in buildAssistantPartsForPersist is sufficient for this contract.

F31 (control characters in filenames) acknowledged by author but no ticket linked. The deferral promise without a ticket is a drop, not a deferral. This needs a human decision.

2 new P3, 1 Nit. The remaining open findings are minor: a missing integration-level test for the content-validation gate in storeChatAttachment, recording logs that lack entity IDs for production triage, and a stale comment referencing a deleted constant.

"If someone refactored storeChatAttachment and dropped or moved the PrepareStoredFile call, no test would break." (Bisky, on the missing rejection test)


coderd/exp_chats_test.go:6881

Nit Comment references deleted constant maxChatFileName. Update to chatfiles.MaxStoredFileNameBytes or just "255 bytes". (Netero)

🤖

🤖 This review was automatically generated with Coder Agents.

Comment thread coderd/x/chatd/store_chat_attachment_test.go
Comment thread coderd/x/chatd/recording.go Outdated
Copy link
Copy Markdown
Member

@johnstcn johnstcn left a comment

Choose a reason for hiding this comment

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

🤖

All 4 findings from our R1 review are confirmed fixed. The SVG parser now correctly rejects non-whitespace CharData, recordings use transactional insert+link, the duplicate maps are consolidated (with PDF now download-only), and the suggested test cases were added.

The R2 fixes also look solid — PrepareRecordingArtifact byte-verifies recordings against expected types, storeLinkedChatFileTx is shared across both paths, and io.LimitReader caps the agent/base64 reads.

Fresh review found 1 P3 (convergent, 3 reviewers) and 1 test coverage gap across 2 inline comments. No blockers.

Comment thread coderd/chatfiles/mime.go
Comment thread coderd/chatfiles/mime_test.go
@ethanndickson ethanndickson force-pushed the ethan/chat-file-attachments branch from deeba77 to 0e4bde5 Compare April 16, 2026 05:49
@ethanndickson
Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Chef's kiss.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link
Copy Markdown
Member

@mafredri mafredri left a comment

Choose a reason for hiding this comment

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

Round 4 blocked. F33 and F34 were addressed in 0e4bde5 (PrepareStoredFile rejection test and recording log identifiers). F31 was also addressed in the same commit (control character stripping in NormalizeStoredFileName).

F32 (Nit) remains silent: the comment at coderd/exp_chats_test.go:6894 still references the deleted constant maxChatFileName instead of chatfiles.MaxStoredFileNameBytes. This was folded into the R3 review body because it was outside the diff. Further review is blocked until the author responds or pushes a fix for this finding.

🤖 This review was automatically generated with Coder Agents.

@ethanndickson ethanndickson requested a review from mafredri April 16, 2026 07:26
Copy link
Copy Markdown
Member

@mafredri mafredri left a comment

Choose a reason for hiding this comment

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

Round 5. All prior findings resolved. The panel (4 reviewers) verified the R3/R4 fixes: PrepareStoredFile rejection test, recording log identifiers, control character stripping, stale comment update. Hisoka probed 10 distinct attack/edge surfaces and came up empty. The code is in strong shape after 5 rounds and 36 findings tracked across 4 panel reviews.

1 Nit, 1 Note remaining. Both are minor housekeeping.

"Boring code gets silence. This earned it." (Hisoka, after probing upload body ordering, double limits, peak memory, PDF serving, recording artifacts, XML/SVG classification, upload compatibility, cap enforcement, assistant replay, and UTF-8 truncation)

🤖 This review was automatically generated with Coder Agents.

Comment thread coderd/x/chatd/chatprompt/chatprompt_test.go Outdated
Comment thread coderd/chatfiles/mime.go
@ethanndickson ethanndickson requested a review from mafredri April 16, 2026 09:29
Copy link
Copy Markdown
Member

@mafredri mafredri left a comment

Choose a reason for hiding this comment

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

Round 6. All 38 findings across 6 rounds are resolved. Four reviewers found nothing new. Hisoka probed concurrent cap races, multi-byte truncation boundaries, XML passthrough classification, and recording rollback paths with no findings.

This PR tracked 3 P2s, 12 P3s, 4 Nits, 4 Notes, and 1 panel-closed contested finding over 5 panel reviews. Every finding was addressed by the author within one round. The coderd/chatfiles package is a clean extraction with consistent validation contracts, transactional storage, and thorough test coverage (~1.5:1 test-to-code ratio).

"Six rounds in. Every thread I pulled held." (Hisoka)

🤖 This review was automatically generated with Coder Agents.

@ethanndickson ethanndickson force-pushed the ethan/chat-file-attachments branch from 87b1c7e to 83d9c9b Compare April 17, 2026 03:41
@ethanndickson
Copy link
Copy Markdown
Member Author

@mafredri agent is happy :)

@ethanndickson ethanndickson force-pushed the ethan/chat-file-attachments branch from 83d9c9b to cf835bc Compare April 20, 2026 04:10
@ethanndickson
Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🎉

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1285 to +1316
func insertLinkedChatFile(
ctx context.Context,
t *testing.T,
db database.Store,
chatID uuid.UUID,
ownerID uuid.UUID,
organizationID uuid.UUID,
name string,
mediaType string,
data []byte,
) uuid.UUID {
t.Helper()

file, err := db.InsertChatFile(ctx, database.InsertChatFileParams{
OwnerID: ownerID,
OrganizationID: organizationID,
Name: name,
Mimetype: mediaType,
Data: data,
})
require.NoError(t, err)

rejected, err := db.LinkChatFiles(ctx, database.LinkChatFilesParams{
ChatID: chatID,
MaxFileLinks: int32(codersdk.MaxChatFileIDs),
FileIds: []uuid.UUID{file.ID},
})
require.NoError(t, err)
require.Zero(t, rejected)

return file.ID
}
Copy link
Copy Markdown
Member

@johnstcn johnstcn Apr 20, 2026

Choose a reason for hiding this comment

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

reminder for self: make dbfake helpers for this in #24497

@ethanndickson ethanndickson merged commit ef6969d into main Apr 20, 2026
29 of 30 checks passed
@ethanndickson ethanndickson deleted the ethan/chat-file-attachments branch April 20, 2026 08:04
@github-actions github-actions Bot locked and limited conversation to collaborators Apr 20, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants