Skip to content

fix(attachments): make --replace work on Data Center/Server - #257

Merged
pchuri merged 1 commit into
pchuri:mainfrom
bestend:fix/dc-attachment-replace
Oct 1, 2026
Merged

pchuri merged 1 commit into
pchuri:mainfrom
bestend:fix/dc-attachment-replace

Conversation

@bestend

@bestend bestend commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

Problem

confluence attachment-upload <pageId> --file a.html --replace fails on Confluence Data Center/Server with Request failed with status code 405.

--replace sends PUT /rest/api/content/{pageId}/child/attachment. That "create or update attachment" endpoint exists in the Confluence Cloud v1 API, but the Data Center REST API has no PUT on the attachment collection: it offers POST .../child/attachment (create only) and POST .../child/attachment/{attachmentId}/data (replace the data of an existing attachment). On Data Center, a filename that already exists can therefore be neither uploaded again (400) nor replaced (405).

Observed on Confluence Data Center 9.2.9 (PAT bearer auth, apiPath /rest/api):

Request Result
attachment-upload <pageId> --file a.html OK, new attachment
same file again, without --replace HTTP 400 Cannot add a new attachment with same file name as an existing attachment: a.html
same file again, with --replace Request failed with status code 405
POST /rest/api/content/{pageId}/child/attachment/{attachmentId}/data (multipart file, header X-Atlassian-Token: nocheck, optional minorEdit/comment) HTTP 200, attachment version incremented, content replaced

Fix

On Data Center/Server (!isCloud()), --replace now looks the attachment up by exact filename (GET .../child/attachment?filename=<name>) and POSTs the file to .../child/attachment/{attachmentId}/data. When the page has no attachment with that name, it does the regular create POST. comment and minorEdit are sent as before.

  • Cloud (*.atlassian.net, forceCloud, scoped tokens) keeps the existing PUT, which is an atomic create-or-update, so its behavior and request count are unchanged.
  • Only an exact title match selects the target, so a server that ignores the filename filter cannot make the CLI overwrite another attachment.
  • POST .../{attachmentId}/data answers with the updated attachment itself (a single object with type: "attachment") on Data Center 9.2.9, as the Cloud reference documents, while older Data Center references show { results: [...] }. uploadAttachment accepts both, so results (and the ID/Version output of the command) stays uniform.
  • The attachment ID is URL-encoded before it goes into the path.
  • README and SKILL.md now state the --replace semantics.

Verification

  • Added 13 unit tests using the existing axios-mock-adapter approach: Data Center replace through POST .../{id}/data (headers, multipart body, comment/minorEdit), create when nothing matches, exact-title selection, special characters in the filename, ID encoding, single-object and { results } responses, failed lookup, no lookup without --replace, and Cloud unchanged for *.atlassian.net, forceCloud and scoped tokens. 9 of them fail on main with the 405 above; the other 4 guard the unchanged paths.
  • npm test: 1466 passed (1453 before). npm run lint: clean.
  • Ran this branch against the Confluence Data Center 9.2.9 server on a throwaway page: v2.25.8 still answers --replace with 405; this branch replaced the attachment twice under the same attachment ID (version 1 → 2 → 3, --comment included), the downloaded file matched the last upload, and --replace with a new filename created a new attachment.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)

Testing

  • Tests pass locally with my changes
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings

🤖 Generated with Claude Code

`attachment-upload --replace` sent PUT /content/{id}/child/attachment.
That "create or update" endpoint exists in the Confluence Cloud v1 API
only; Data Center/Server answers it with 405 Method Not Allowed, so
--replace never worked there.

On Data Center/Server, --replace now looks the attachment up by its
exact filename (GET .../child/attachment?filename=) and updates it with
POST .../child/attachment/{attachmentId}/data. When the page has no
attachment with that name, the file is uploaded with the regular create
POST. The comment and minorEdit fields are sent as before.

- Cloud (*.atlassian.net, forceCloud, scoped tokens) keeps the existing
  PUT, so its behavior and request count are unchanged.
- Only an exact title match selects the target, so a server that ignores
  the filename filter cannot steer the upload at another attachment.
- The data endpoint may answer with a single attachment object instead
  of { results: [...] }; both shapes are normalized into `results`.
- README and the bundled SKILL.md state the --replace semantics.

Observed on Confluence Data Center 9.2.9 (PAT bearer auth): PUT on the
collection returns 405, while POST .../{attachmentId}/data returns 200,
increments the attachment version and replaces the content. The tests
mock these responses; CI has no live server.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@pchuri pchuri left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Thanks for the detailed report, implementation, and Data Center reproduction. The split between the Cloud PUT path and the Data Center/Server lookup-and-POST path looks appropriate, and the exact filename check and response normalization are well covered.

I reviewed the current head and ran the client test suite (269/269 passing) and lint successfully. I did not find a blocking issue in the reviewed changes. I have not independently repeated the live Confluence checks or run the full repository test suite yet, but this looks ready to move toward merge after that final validation. Thanks for the contribution!

@pchuri
pchuri merged commit e9163e4 into pchuri:main Oct 1, 2026
github-actions Bot pushed a commit that referenced this pull request Oct 1, 2026
## [2.25.9](v2.25.8...v2.25.9) (2026-10-01)

### Bug Fixes

* **attachments:** make --replace work on Data Center/Server ([#257](#257)) ([e9163e4](e9163e4))
* **client:** back off when Retry-After is zero or not in the future ([#258](#258)) ([5d9b3b9](5d9b3b9))
* **deps:** update axios to 1.20.0 to unblock security audit ([#263](#263)) ([1f62546](1f62546))
@github-actions

github-actions Bot commented Oct 1, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 2.25.9 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants