Skip to content

feat(jtk): detect attachment MIME type and add --filename override for #465 - #466

Merged
piekstra merged 3 commits into
mainfrom
feat/465-attachment-filename-mimetype
Jul 20, 2026
Merged

feat(jtk): detect attachment MIME type and add --filename override for #465#466
piekstra merged 3 commits into
mainfrom
feat/465-attachment-filename-mimetype

Conversation

@piekstra

Copy link
Copy Markdown
Contributor

Closes #465.

What the issue asked for vs. what already exists

The bulk of #465"attach a file to an issue"already ships today as jtk attachments add <issue-key> --file <path> (list/add/get/delete under the attachments group; API in place since #50, registered in main.go, documented in the README). So this PR does not re-add attach support.

The issue proposed the command as jtk issues attach …, which is the likely reason it read as missing: attach lives in the top-level attachments group, not under issues, so jtk issues --help doesn't surface it. That's a findability gap, not a docs-accuracy gap. I deliberately did not add a duplicate jtk issues attach alias — the repo's GUARDRAILS.md defines attachments/add as the canonical resource/verb, and duplicating it under issues would cut against that design language.

What this PR does implement are the two concrete gaps the issue lists as nice-to-haves, which are the only parts of the existing feature that were genuinely absent.

Changes

  • MIME-type detection on upload. The multipart part's Content-Type is now derived from the file extension (mime.TypeByExtension), falling back to application/octet-stream.
  • --filename override on jtk attachments add — store the attachment under a chosen name instead of the file's base name. Valid with a single --file only (errors clearly otherwise).
  • Test coverage for AddAttachment, which previously had none — asserts the wire filename, the detected Content-Type, the octet-stream fallback, and the empty-arg guards.

Why

The motivating use case in the issue is attaching screenshots. Before this change every upload was sent as application/octet-stream, so Jira didn't recognize a .png/.pdf as an image/document — no thumbnail, no inline preview, it renders as a generic download. Detecting the MIME type from the extension fixes that. --filename covers the common case of uploading a temp file with an ugly generated name (/tmp/out-8f8a7b.png) but wanting it stored as something meaningful (before.png).

Deliberately deferred

The issue's optional ask — referencing an uploaded attachment inline in a comment/description via Jira's !filename! media syntax — is not included. jtk renders comments/descriptions as ADF (v3 API), and the shared adf package has no media-node support; wiring inline embeds would need new shared node types plus a media-services UUID the v3 attachment API doesn't return, and it can't be verified without a live Jira. That's a larger, separately-scoped change and is left for a follow-up.

Testing

  • go test -race ./tools/jtk/... — pass
  • golangci-lint run (tools/jtk) — 0 issues
  • go mod tidy — clean; cross-compile release guard (linux/windows) — pass
  • Verified on the built binary: --filename appears in attachments add --help; the single---file validation errors with exit 1 before auth is attempted.

…#465

Uploads previously sent every file as application/octet-stream, so a
screenshot attached to an issue wasn't recognized as an image by Jira
(no thumbnail or inline preview). Derive the multipart part's Content-Type
from the file extension, falling back to octet-stream for unknown types.

Add a --filename flag to jtk attachments add so an attachment can be stored
under a chosen name instead of the file's base name (valid with a single
--file only). Also adds the previously-missing AddAttachment test coverage.
@piekstra
piekstra requested a review from piekstra-dev July 20, 2026 10:59

@piekstra-dev piekstra-dev left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Automated PR Review

Reviewed commit: 029f0bae0488
Profile: reviewer - Posting as: piekstra-dev

Summary

Reviewer Findings
go:implementation-tests 2
policies:conventions 0
go:implementation-tests (2 findings)

Major - tools/jtk/api/attachments.go:23

quoteEscaper and its use in attachmentPartHeader (line 215) exist specifically so a user-supplied --filename override containing a " or \ cannot break out of the Content-Disposition header — the doc comment says so explicitly. Before this PR the stored name was always filepath.Base(filePath), a value the OS already constrains; now it can be arbitrary user input via --filename, making this escaping the load-bearing safety net for a new, untrusted input path. Neither attachments_test.go (api) nor the cmd-level tests exercise an override containing a quote or backslash, so a regression in the escaping (e.g. reordering the strings.NewReplacer pairs, or dropping the call) would ship undetected. Add a case to TestAddAttachment with override: before"exploit.png`" (or similar) and assert the multipart reader still parses a single part with the expected literal filename, proving the header isn't corrupted.

Minor - tools/jtk/internal/cmd/attachments/attachments_test.go:307

TestRunAdd_FilenameRequiresSingleFile only proves the guard clause rejects --filename with multiple files; no test drives runAdd end-to-end with a single file plus --filename and asserts the server-observed filename matches the override. AddAttachment(ctx, issueKey, filePath, filename string) takes three same-typed string args, so a copy-paste/order regression in runAdd's call (attachments.go:157) wouldn't be caught by the api-layer test (which calls client.AddAttachment directly) or by this guard-only test. Add a success-path test mirroring TestRunAdd_Success that passes a non-empty filename and asserts the httptest server received that name via part.FileName().

Reviewer Coverage

Reviewer Status Inspected Skipped Constraints
go:implementation-tests complete_broad tools/jtk/api/attachments.go, tools/jtk/api/attachments_test.go, tools/jtk/internal/cmd/attachments/attachments.go, tools/jtk/internal/cmd/attachments/attachments_test.go unavailable unavailable
policies:conventions complete_broad tools/jtk/README.md, tools/jtk/internal/cmd/attachments/attachments.go unavailable unavailable

0 PR discussion threads considered. 0 summarized; 0 resolved.


Completed in 3m 16s | unavailable | claude-sonnet-5 | cr 0.10.264
Field Value
Model claude-sonnet-5
Reviewers go:implementation-tests, policies:conventions
Engine claude_cli · claude-sonnet-5
Reviewed by cr · piekstra-dev
Duration 3m 16s wall · 4m 39s compute
Cost unavailable
Tokens unavailable

Per-workstream usage

Workstream Model In Out Cache read Cache create Cost Duration
orchestrator-selection claude-sonnet-5 unavailable unavailable unavailable unavailable unavailable 36s
go:implementation-tests claude-sonnet-5 unavailable unavailable unavailable unavailable unavailable 2m 14s
policies:conventions claude-sonnet-5 unavailable unavailable unavailable unavailable unavailable 1m 34s
orchestrator-rollup claude-sonnet-5 unavailable unavailable unavailable unavailable unavailable 14s

Comment thread tools/jtk/api/attachments.go
Comment thread tools/jtk/internal/cmd/attachments/attachments_test.go
Address review findings on #466:

- TestAddAttachment now exercises override names containing a quote and a
  backslash, asserting the filename round-trips literally and that exactly
  one multipart part is produced — locking in quoteEscaper as the safety
  net for the new untrusted --filename input.
- TestRunAdd_FilenameOverride drives runAdd end-to-end with a single file
  plus --filename and asserts the server observes the overridden name,
  guarding the cmd-layer call against arg-order regressions.
@piekstra
piekstra requested a review from piekstra-dev July 20, 2026 11:10

@piekstra-dev piekstra-dev left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Automated PR Review

Reviewed commit: 4733699c161f
Profile: reviewer - Posting as: piekstra-dev

Summary

Reviewer Findings
go:implementation-tests 0
policies:conventions 0

Reviewer Coverage

Reviewer Status Inspected Skipped Constraints
go:implementation-tests complete_broad tools/jtk/api/attachments.go, tools/jtk/api/attachments_test.go, tools/jtk/internal/cmd/attachments/attachments.go, tools/jtk/internal/cmd/attachments/attachments_test.go unavailable unavailable
policies:conventions complete_broad tools/jtk/README.md, tools/jtk/internal/cmd/attachments/attachments.go unavailable unavailable

0 PR discussion threads considered. 0 summarized; 0 resolved.


Completed in 2m 39s | unavailable | claude-sonnet-5 | cr 0.10.264
Field Value
Model claude-sonnet-5
Reviewers go:implementation-tests, policies:conventions
Engine claude_cli · claude-sonnet-5
Reviewed by cr · piekstra-dev
Duration 2m 39s wall · 3m 11s compute
Cost unavailable
Tokens unavailable

Per-workstream usage

Workstream Model In Out Cache read Cache create Cost Duration
orchestrator-selection claude-sonnet-5 unavailable unavailable unavailable unavailable unavailable 15s
go:implementation-tests claude-sonnet-5 unavailable unavailable unavailable unavailable unavailable 1m 49s
policies:conventions claude-sonnet-5 unavailable unavailable unavailable unavailable unavailable 55s
orchestrator-rollup claude-sonnet-5 unavailable unavailable unavailable unavailable unavailable 11s

@piekstra
piekstra merged commit cc4700d into main Jul 20, 2026
10 checks passed
@piekstra
piekstra deleted the feat/465-attachment-filename-mimetype branch July 20, 2026 11:18
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.

jtk: add file attachment / image embed support for issues and comments

2 participants