Feature: Reach the files area, and attach a file more than once - #490
Merged
Conversation
CFDan
force-pushed
the
feature/project-files-section
branch
from
August 26, 2026 08:21
2740fb8 to
e93cbc4
Compare
Nothing called projects.FileCreate, so a file could only ever arrive as a side effect of attaching it to a task, a comment or a message. There was no way to store one on its own, or to describe, categorise or tag it. That left a sharper gap behind it. A pending reference is spent by the first attachment, and the files area is the only route to an identifier that is not — but parseTaskAttachments filled TaskAttachments.PendingFiles and never .Files, so nothing could consume one. A single document that belongs to several tasks had to be uploaded once per task. twprojects-add_project_file stores an uploaded file in a project's files area and answers with its ID. attachment_file_ids on create_task and update_task accepts those IDs alongside attachment_refs. They are separate parameters rather than one that takes either, because the two are not interchangeable: one is spent by the first attachment and the other is not. Telling them apart by the "tf_" prefix would encode a guess about the identifier format that even the SDK declines to make. Messages can also carry existing file IDs, through the flat attachments field, and are deliberately left for a follow-up rather than widened here. Costs +665 tokens on the tool definitions, per cmd/mcp-tokens. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
rafaeljusto
force-pushed
the
feature/project-files-section
branch
from
August 26, 2026 12:32
e93cbc4 to
28ed7a4
Compare
Contributor
rafaeljusto
approved these changes
Aug 26, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.




Stacked on #489 — review that first. Base is
feature/create-upload-url, so this diff showsonly the files-area work.
Why
Chasing the customer's upload complaint turned up two gaps behind it.
Nothing calls
projects.FileCreate. A file could only ever arrive in a project's filesarea as a side effect of being attached to a task, comment or message. There was no way to
store one on its own, or to give it a description, category or tags.
twprojects-create_fileis named for this and does something else — it produces a pending reference.
A pending reference is spent by the first attachment, and the files area is the only
route to an identifier that is not. But
parseTaskAttachmentsfilledTaskAttachments.PendingFilesand never.Files, so nothing could consume one. A singledocument belonging to three tasks had to be uploaded three times.
That second one is the customer's actual use case — one insurance certificate referenced
from several tasks — even though it is not what they filed.
What
twprojects-add_project_file {project_id, reference, …}stores an uploaded file in aproject's files area and answers with its ID.
attachment_file_idsoncreate_taskandupdate_taskaccepts those IDs alongsideattachment_refs.Why two parameters rather than one that takes either: they are not interchangeable —
one is spent by the first attachment and the other is not — and telling them apart by the
tf_prefix would encode a guess about the identifier format that the SDK itself declinesto make (it never parses or constructs that prefix).
Verification
go build ./... && go vet ./... && go test ./...all pass; docs regenerated.Per
AGENTS.md, the mocks reply the same either way, so the tests assert on the encodedbody rather than on the call succeeding:
pendingFileRefand the optional fields reach thePOST /projects/{id}/files.jsonbody, and both attachment forms survive together on a task.TestTaskAttachmentsOmittedWithoutEitheris the regression guard — adding a secondparameter must not start sending an empty
attachmentsobject on every task. Thepre-existing
TestTaskAttachmentsOmittedWhenNotRequestedin the SDK still passes too.Token cost, per
go run ./cmd/mcp-tokens -base=main: +665 for this PR (+982 for thestack).
add_project_file+499, and +83 each oncreate_task/update_task.Open questions for you, @rafaeljusto
.Filesleft out deliberately? You restricted comments to pending refs only fora stated reason (the endpoint takes nothing else). If the task
.Filesomission was alsodeliberate rather than incidental, say so and I will drop that half — I could not tell
from the code which it was.
create_fileproduces a reference,add_project_fileconsumes one. I avoidedrenaming
create_filebecause it is shipped and clients depend on it, and insteadrewrote its description to say what it actually returns. Still not lovely.
MessageCreateRequest.Attachmentsis aLegacyNumericListthat would take file IDs the same way, but that is outside the threeflows this work was scoped to, so it is a follow-up rather than a quiet widening.
🤖 Generated with Claude Code