Skip to content

feat: track chat file associations with chat_file_links on chats - #23537

Merged
johnstcn merged 2 commits into
mainfrom
cian/files-tab-rhs-panel
Apr 7, 2026
Merged

johnstcn merged 2 commits into
mainfrom
cian/files-tab-rhs-panel

Conversation

@johnstcn

@johnstcn johnstcn commented Mar 24, 2026 •

Copy link
Copy Markdown
Member

Needed by #23833

Adds a chat_file_links association table to track which files are associated with each chat.

  • AppendChatFileIDs query links a file to a chat with deduplication
  • GetChatFileMetadataByIDs query returns lightweight file metadata by IDs
  • Tool-created files (e.g. propose_plan) are linked to the chat after insert
  • User-uploaded files are linked to the chat when the referencing message is sent
  • Single-chat GET endpoint hydrates files: ChatFileMetadata[] on the response

🤖 Created by Coder Agents and massaged into shape by a human.

@johnstcn johnstcn changed the title feat: add Files tab to Agents right-hand panel [DNM] feat(coderd): track chat file associations with file_ids on chats Mar 25, 2026
johnstcn

This comment was marked as outdated.

@johnstcn johnstcn left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

read with mine own two eyes and deemed acceptable

@johnstcn johnstcn changed the title [DNM] feat(coderd): track chat file associations with file_ids on chats feat(coderd): track chat file associations with file_ids on chats Mar 25, 2026
@johnstcn
johnstcn marked this pull request as ready for review March 25, 2026 22:18
Copilot AI review requested due to automatic review settings March 25, 2026 22:18

Copilot AI left a comment

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.

Pull request overview

Backend-only change to persist and expose chat↔file associations so the Agents UI can later render a “Files” tab.

Changes:

  • Add chats.file_ids uuid[] with migration + dump updates, and plumb it through generated DB models/queries.
  • Add DB queries to append file IDs with deduplication and to fetch lightweight file metadata by ID.
  • Link referenced file IDs on message creation/edit and tool-created file insert; hydrate Chat.files on single-chat GET.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
site/src/api/typesGenerated.ts Adds Chat.files?: ChatFileMetadata[] and the ChatFileMetadata type to generated frontend API types.
codersdk/chats.go Extends SDK Chat response with Files and defines ChatFileMetadata.
coderd/x/chatd/chatd.go Links tool-created chat files to the chat via AppendChatFileIDs (best-effort).
coderd/exp_chats.go Appends referenced file IDs on create/send/edit; hydrates files metadata on GET /chat.
coderd/database/queries/chats.sql Adds AppendChatFileIDs update query for chats.file_ids.
coderd/database/queries/chatfiles.sql Adds GetChatFileMetadataByIDs metadata-only query.
coderd/database/queries.sql.go Regenerates sqlc output for new queries/columns.
coderd/database/querier.go Adds new sqlc querier interface methods.
coderd/database/models.go Adds Chat.FileIds field for file_ids.
coderd/database/modelqueries.go Updates chat scan to include file_ids in authorized chat listing.
coderd/database/migrations/000452_add_chat_file_ids.up.sql Adds file_ids uuid[] NOT NULL DEFAULT '{}' to chats.
coderd/database/migrations/000452_add_chat_file_ids.down.sql Drops the file_ids column.
coderd/database/dump.sql Updates schema dump for chats.file_ids.
coderd/database/dbmock/dbmock.go Extends mock store with new query methods.
coderd/database/dbmetrics/querymetrics.go Adds metrics wrappers for the new query methods.
coderd/database/dbauthz/dbauthz_test.go Adds RBAC wrapper tests for the new queries.
coderd/database/dbauthz/dbauthz.go Adds dbauthz wrappers for AppendChatFileIDs and GetChatFileMetadataByIDs.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread coderd/exp_chats.go Resolved
Comment thread coderd/database/dbauthz/dbauthz.go Resolved
Comment thread coderd/database/dbauthz/dbauthz.go Resolved
Comment thread coderd/database/queries/chats.sql Resolved
@coderagents

This comment was marked as outdated.

@johnstcn johnstcn changed the title feat(coderd): track chat file associations with file_ids on chats feat: track chat file associations with file_ids on chats Mar 25, 2026
@johnstcn
johnstcn requested review from kylecarbs and mafredri and removed request for kylecarbs and mafredri March 25, 2026 22:55
@johnstcn
johnstcn force-pushed the cian/files-tab-rhs-panel branch 2 times, most recently from 146e39d to be2a333 Compare March 26, 2026 09:26

@mafredri mafredri left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Clean approach to linking files with chats. The file_ids UUID[] column mirrors the existing mcp_server_ids pattern, the SQL dedup via unnest + array_agg(DISTINCT) is solid, and the dbauthz wiring is correct.

1 P1, 2 P2, 3 P3, 2 Obs, 4 nits across 12 inline comments.

Meruem: "convertChatFileMetadata maps four of six fields and skips two. Every GET /chats/{chat} response with files will include owner_id: 00000000-... for each file."

Hisoka: "Three handlers call appendChatFileIDs. All three only append. No code path ever removes a UUID from chats.file_ids. What happens when someone uploads twenty images across ten edit cycles? Forty files in the panel, half of them ghosts."

🤖 This review was generated with the help of Coder Agents.

Comment thread coderd/exp_chats.go Resolved
Comment thread coderd/exp_chats.go Resolved
Comment thread coderd/exp_chats.go Resolved
Comment thread coderd/exp_chats.go Resolved
Comment thread docs/ai-coder/agents/chats-api.md Resolved
Comment thread coderd/exp_chats.go Resolved
Comment thread codersdk/chats.go Resolved
Comment thread coderd/database/queries/chats.sql Resolved
Comment thread coderd/database/queries/chatfiles.sql Resolved
Comment thread coderd/x/chatd/chatd.go Resolved
mafredri

This comment was marked as duplicate.

@johnstcn
johnstcn force-pushed the cian/files-tab-rhs-panel branch from be2a333 to 33219aa Compare March 27, 2026 15:33

Copy link
Copy Markdown
Member Author

Pushed 4 commits addressing the outstanding review comments:

Changes

  • P1 fix: db2sdk.Chat() now accepts files []database.GetChatFileMetadataByIDsRow as a third parameter (mirrors the existing diffStatus pattern). All 6 fields are mapped correctly — OwnerID and OrganizationID are no longer zero-valued. The standalone db2sdk.ChatFileMetadata() function is deleted.
  • Nit fix: JSON tag on ChatFileMetadata.MimeType changed from "mimetype" to "mime_type" (snake_case, consistent with owner_id, created_at, and chatprompt.persistedMediaResult).
  • P2 fix: 3 new behavioral tests: TestGetChat/FilesHydrated, TestChatMessageWithFiles/FilesLinkedOnSend, TestChatMessageWithFiles/DedupFileIDs.
  • P3 refactor: createChatInputFromParts now returns fileIDs alongside content. The double-scan appendChatFileIDs is replaced by a simpler linkFilesToChat helper. Comment added explaining file-reference exclusion.
  • P3 docs: Softened "all files associated" → "files that have been successfully linked". Noted that files is only populated on the single-chat GET endpoint.

Not addressed (out-of-scope per earlier discussion)

  • Append-only semantics / orphan pruning (future dbpurge work)
  • Consolidating file linking into InsertChatFile wrapper

🤖 This response was generated by Coder Agents.

@mafredri mafredri left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Round 2 review. The prior round's findings were substantially addressed: OwnerID/OrganizationID mapping, behavioral tests, stale response hydration, docs accuracy, input-part re-scan, JSON tag consistency, and query comments are all resolved. Good work.

Remaining: 4x P2, 4x P3, 3x Note. One P2 is re-raised from a deferral with no tracking ticket.

"The interest rate: an active AI coding session using propose_plan appends a file per tool call. Twenty iterations means twenty UUIDs. That is harmless today. A hundred sessions on the same chat thread means the array approaches sizes where the unnest-reaggregate UPDATE starts showing up in query latency, and the per-row overhead in list queries stops being free. There is no cap on the array, no dbpurge job, and no ticket tracking one." -- Knuckle

🤖 This review was automatically generated with Coder Agents.

Comment thread coderd/database/queries/chats.sql Resolved
Comment thread coderd/x/chatd/chatd.go Resolved
Comment thread coderd/database/models.go Resolved
Comment thread coderd/exp_chats.go Resolved
Comment thread coderd/exp_chats.go Resolved
Comment thread coderd/database/db2sdk/db2sdk.go Resolved
Comment thread docs/ai-coder/agents/chats-api.md Resolved
Comment thread coderd/database/queries/chats.sql Resolved
Comment thread coderd/x/chatd/chatd.go Resolved
Comment thread coderd/database/db2sdk/db2sdk.go Resolved

Copy link
Copy Markdown
Member Author

Self-review findings

Ran a deep self-review. Two P2 findings are being addressed (plan approved, implementation next). Two lower-severity items noted below as accepted tradeoffs.

Being fixed

  • P2: Message committed before cap check. postChatMessages and patchChatMessage persist the message before calling linkFilesToChat, so a cap rejection returns 400 on an already-committed message. Fix: move cap validation before SendMessage/EditMessage.
  • P2: Stale chatd snapshot allows cap bypass in multi-file turns. StoreFile reads chatSnapshot.FileIDs once per turn but never refreshes after AppendChatFileIDs. Also, InsertChatFile runs before the cap check, orphaning the row on rejection. Fix: reorder cap check before insert, update snapshot after append.

Accepted tradeoffs

  • P3: linkFilesToChat double-counts already-linked file IDs. If a user re-references an already-linked file near the cap boundary, the Go check is overly conservative because it doesn't subtract the overlap (SQL DISTINCT handles it). This is intentional — it's easier to raise a limit than to lower one, and the edge case (re-referencing files at exactly the cap boundary) is rare.
  • Obs: array_agg(DISTINCT) has no ORDER BY. Each AppendChatFileIDs call may reorder the stored file_ids array. No correctness impact since the array is used as a set (lookups go through GetChatFileMetadataByIDs which applies its own ORDER BY created_at ASC). Adding ORDER BY fid would make storage deterministic at negligible cost but is not worth the diff churn right now.

🤖 This response was generated by Coder Agents.

@mafredri mafredri left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Round 3. The core feature is solid: the migration is safe, the SQL dedup works, db2sdk conversion maps all fields, and tests now cover the critical paths. The round 1/2 fixes addressed most findings. This round's six reviewers converged on the cap enforcement logic.

2x P2, 1x P3, 2x Note. Five still-open nits/notes from round 1.

Mafuuu: "Once a chat reaches MaxChatFileIDs, no message or edit can reference any file at all, even ones already linked. The chat becomes file-hostile at its own cap."

Knuckle's center-of-gravity recommendation: move the cap enforcement into the SQL AppendChatFileIDs query itself (e.g. WHERE array_length(file_ids, 1) < 20), which would eliminate the double-counting bug, the TOCTOU race, and the snapshot divergence in one structural change. The SQL already does the dedup. Let it also do the cap.

5 unresolved nits/notes from round 1 with no author response: #comment-2993904968 (DISTINCT comment), #comment-2993904974 (metadata query comment), #comment-3008493046 (updated_at bump on no-op), #comment-3008493049 (pubsub omits files), #comment-2993904980 (two independent linking paths).

🤖 This review was automatically generated with Coder Agents.

Comment thread coderd/exp_chats.go Resolved
Comment thread coderd/exp_chats.go Resolved
Comment thread coderd/x/chatd/chatd.go Resolved
Comment thread coderd/exp_chats.go Resolved
Comment thread coderd/exp_chats_test.go Resolved
@johnstcn

Copy link
Copy Markdown
Member Author

Smoke-tested locally
Screenshot 2026-03-31 at 16 56 40

@johnstcn
johnstcn requested a review from mafredri March 31, 2026 15:57

@mafredri mafredri left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Round 4. The latest commit is a strong structural improvement. Moving cap enforcement into SQL via AppendChatFileIDs :execrows with a WHERE array_length(...) <= @max_file_ids guard eliminates four findings from round 3 in one change: the double-counting bug, the TOCTOU race, the snapshot divergence, and the error-classification problem. The conditional updated_at bump addresses a round-1 nit. Best-effort linking with warnings is a clean contract.

1x P2, 1x P3, 2x Note. No blockers.

Knuckle: "The CTE subquery reads a stale snapshot under concurrent access, losing file IDs. One-line fix: FOR UPDATE." Mafuuu independently flagged the same CTE concurrency gap.

3 unresolved nits from round 1 with no author response: #comment-2993904968 (DISTINCT comment), #comment-2993904974 (metadata query comment), #comment-2993904980 (two independent linking paths).

🤖 This review was automatically generated with Coder Agents.

Comment thread coderd/database/queries/chats.sql Resolved
Comment thread coderd/exp_chats.go Resolved
Comment thread coderd/exp_chats_test.go Resolved

@johnstcn johnstcn left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

self-re-reviewed

@johnstcn
johnstcn requested a review from mafredri March 31, 2026 21:47

@mafredri mafredri left a comment

Copy link
Copy Markdown
Member

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 addressed. No new blockers.

The FOR UPDATE in the CTE serializes concurrent appends correctly. linkFilesToChat now distinguishes DB errors from cap exceeded with accurate user-facing messages. Test coverage for warning paths is solid: FileCapExceeded, FileCapOnCreate, DedupFileIDs, and ToolCreatedFilesLinked all verify the right behavior at boundaries. The round-1 nits (DISTINCT comment, metadata query comment, pubsub comment) have replies and code changes.

One design-level observation (not a blocker): AppendChatFileIDs applies all-or-nothing batch semantics. Sending 21 files when the cap is 20 links 0, not 20. The FileCapOnCreate test documents this explicitly. If partial linking is desired later, the SQL would need per-element insertion or a different CTE structure.

The deferred append-only/no-pruning issue (coder/internal#1434) and the two-independent-paths observation remain as known design debt.

Mafuuu, on the batch semantics: "A user one file over the limit gets nothing linked instead of getting the maximum possible. This is a user-visible rough edge that the future Files tab will surface."

🤖 This review was automatically generated with Coder Agents.

Comment thread coderd/exp_chats.go Resolved

@johnstcn johnstcn left a comment •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Round 5 self-review (post-merge with main). All prior-round findings are resolved or acknowledged. The merge was clean after conflict resolution and make gen/fmt/lint pass. 8 reviewers, 0 new blockers.

1 P2, 2 P3, 1 Nit across 4 inline comments. Two observations noted inline.

🤖 This review was automatically generated with Coder Agents.

Comment thread coderd/exp_chats.go Resolved
Comment thread coderd/exp_chats.go Resolved
Comment thread coderd/exp_chats.go Resolved
Comment thread coderd/database/db2sdk/db2sdk.go Resolved
@johnstcn

johnstcn commented Apr 2, 2026

Copy link
Copy Markdown
Member Author

Human feedback: join on chat_files table instead of adding file_ids.

@johnstcn
johnstcn marked this pull request as draft April 2, 2026 14:20

@johnstcn johnstcn left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Round 6 self-review (post join-table refactor). 5 reviewers, focused on the database refactor and cross-checking against all prior mafredri comments.

Fixed: P1 migration missing FK constraints, P2 migration number collision with main, P3 collapsed nolint directive, stale comment. Pushed as afb9be4ac.

1 accepted tradeoff noted below.

🤖 This review was automatically generated with Coder Agents.

Comment thread coderd/database/queries/chats.sql Resolved

johnstcn commented Apr 2, 2026 •

Copy link
Copy Markdown
Member Author

🤖 Heads up: #23833 (chat data retention / dbpurge) depends on the chat_file_links column added here. The purge job needs to know which chat_files rows are still referenced by active chats — without it, DeleteOldChatFiles can't distinguish orphaned files from files in use.

@johnstcn
johnstcn marked this pull request as ready for review April 2, 2026 17:42
@johnstcn johnstcn changed the title feat: track chat file associations with file_ids on chats feat: track chat file associations with chat_files on chats Apr 2, 2026
@johnstcn johnstcn changed the title feat: track chat file associations with chat_files on chats feat: track chat file associations with chat_file_links on chats Apr 2, 2026
@johnstcn
johnstcn force-pushed the cian/files-tab-rhs-panel branch from 31e70c4 to 759b919 Compare April 7, 2026 08:16
Adds a chat_file_links association table to track which files are
associated with each chat.

- AppendChatFileIDs query links a file to a chat with deduplication
- GetChatFileMetadataByIDs query returns lightweight file metadata by IDs
- Tool-created files (e.g. propose_plan) are linked to the chat after insert
- User-uploaded files are linked to the chat when the referencing message is sent
- Single-chat GET endpoint hydrates files: ChatFileMetadata[] on the response

Rebased on latest main with migration renumbered to 000462.
@johnstcn
johnstcn force-pushed the cian/files-tab-rhs-panel branch from 759b919 to 3482b8a Compare April 7, 2026 08:19
@johnstcn
johnstcn merged commit d5a1792 into main Apr 7, 2026
29 of 31 checks passed
@johnstcn
johnstcn deleted the cian/files-tab-rhs-panel branch April 7, 2026 11:05
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.

4 participants