Skip to content

Feature: Reach the files area, and attach a file more than once - #490

Merged
rafaeljusto merged 1 commit into
mainfrom
feature/project-files-section
Aug 26, 2026
Merged

Feature: Reach the files area, and attach a file more than once#490
rafaeljusto merged 1 commit into
mainfrom
feature/project-files-section

Conversation

@CFDan

@CFDan CFDan commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Stacked on #489 — review that first. Base is feature/create-upload-url, so this diff shows
only 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 files
area 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_file
is 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 parseTaskAttachments filled
TaskAttachments.PendingFiles and never .Files, so nothing could consume one. A single
document 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 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.

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 declines
to 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 encoded
body rather than on the call succeeding: pendingFileRef and the optional fields reach the
POST /projects/{id}/files.json body, and both attachment forms survive together on a task.

TestTaskAttachmentsOmittedWithoutEither is the regression guard — adding a second
parameter must not start sending an empty attachments object on every task. The
pre-existing TestTaskAttachmentsOmittedWhenNotRequested in the SDK still passes too.

Token cost, per go run ./cmd/mcp-tokens -base=main: +665 for this PR (+982 for the
stack). add_project_file +499, and +83 each on create_task/update_task.

Open questions for you, @rafaeljusto

  1. Was .Files left out deliberately? You restricted comments to pending refs only for
    a stated reason (the endpoint takes nothing else). If the task .Files omission was also
    deliberate rather than incidental, say so and I will drop that half — I could not tell
    from the code which it was.
  2. Naming. create_file produces a reference, add_project_file consumes one. I avoided
    renaming create_file because it is shipped and clients depend on it, and instead
    rewrote its description to say what it actually returns. Still not lovely.
  3. Messages are deliberately untouched. MessageCreateRequest.Attachments is a
    LegacyNumericList that would take file IDs the same way, but that is outside the three
    flows this work was scoped to, so it is a follow-up rather than a quiet widening.

🤖 Generated with Claude Code

@CFDan
CFDan force-pushed the feature/project-files-section branch from 2740fb8 to e93cbc4 Compare August 26, 2026 08:21
Base automatically changed from feature/create-upload-url to main August 26, 2026 12:24
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
rafaeljusto force-pushed the feature/project-files-section branch from e93cbc4 to 28ed7a4 Compare August 26, 2026 12:32
@rafaeljusto

Copy link
Copy Markdown
Contributor

Working as expected!

twprojects-add_project_file

{
  "name": "another-test.txt",
  "project_id": 6,
  "reference": "tf_b4a009bc-9a89-46bd-a60c-609ba2fd6437.txt"
}
image image

twprojects-create_task

{
  "attachment_file_ids": [
    2
  ],
  "name": "Task 6",
  "tasklist_id": 5
}
image image

@rafaeljusto
rafaeljusto marked this pull request as ready for review August 26, 2026 12:57
@rafaeljusto
rafaeljusto requested a review from a team as a code owner August 26, 2026 12:57
@rafaeljusto
rafaeljusto merged commit 4b689e6 into main Aug 26, 2026
4 checks passed
@rafaeljusto
rafaeljusto deleted the feature/project-files-section branch August 26, 2026 12:57
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.

2 participants