Skip to content

fix: prepend /wiki context path in toAbsoluteUrl for Cloud attachment downloads#99

Closed
adamtan945 wants to merge 1 commit intopchuri:mainfrom
adamtan945:fix/attachment-download-cloud-context-path
Closed

fix: prepend /wiki context path in toAbsoluteUrl for Cloud attachment downloads#99
adamtan945 wants to merge 1 commit intopchuri:mainfrom
adamtan945:fix/attachment-download-cloud-context-path

Conversation

@adamtan945
Copy link
Copy Markdown

Summary

Fixes #98.

confluence attachments <pageId> --download always fails with Error: Request failed with status code 404 on Atlassian Cloud, even though listing attachments on the same page works correctly.

Root cause

The REST API returns each attachment's _links.download as a path relative to the Confluence context root, e.g.:

/download/attachments/123456789/example.png?version=1&modificationDate=1700000000000&cacheVersion=1&api=v2

On Atlassian Cloud the context root is /wiki, so the correct absolute URL is:

https://example.atlassian.net/wiki/download/attachments/123456789/example.png?version=...

But downloadAttachment calls toAbsoluteUrl(attachment._links?.download) (lib/confluence-client.js:864), which goes straight through buildUrl and produces:

https://example.atlassian.net/download/attachments/123456789/example.png?version=...

Missing the /wiki prefix. Cloud returns 404 for that URL.

The interesting part: the constructor already computes this.webUrlPrefix = this.apiPath.startsWith('/wiki/') ? '/wiki' : '' on line 38, and it's correctly prepended in four places when building web UI links (lines 416, 510, 1356, 1360). But it was never applied inside toAbsoluteUrl, so attachment downloads (and the downloadLink field on formatted attachments, line 2063) fell through the gap.

Query parameters (version, modificationDate, cacheVersion) are all preserved correctly — the original issue hypothesis about missing query params was wrong. The only thing missing is the context path.

Fix

Prepend webUrlPrefix inside toAbsoluteUrl when the input is a relative path and doesn't already include the prefix. Server/DC behavior is unchanged because webUrlPrefix is an empty string on those instances.

toAbsoluteUrl(pathOrUrl) {
  if (!pathOrUrl) return null;
  if (pathOrUrl.startsWith('http://') || pathOrUrl.startsWith('https://')) {
    return pathOrUrl;
  }

  const prefix = this.webUrlPrefix || '';
  const needsPrefix = prefix && !pathOrUrl.startsWith(`${prefix}/`);
  const withPrefix = needsPrefix ? `${prefix}${pathOrUrl}` : pathOrUrl;

  return this.buildUrl(withPrefix);
}

The double-prefix guard (!pathOrUrl.startsWith(\${prefix}/`)) protects against future API changes where _links.downloadmight start returning paths that already include/wiki`.

Tests

Added three targeted tests to tests/confluence-client.test.js:

  1. Cloud client prepends /wiki to a relative download path with full query string
  2. Cloud client does not double-prepend when the path already starts with /wiki/
  3. Server/DC client (no webUrlPrefix) leaves relative paths untouched — regression guard

Full suite: 174 passed / 174 total (was 171 before).

Test plan

  • npx jest tests/confluence-client.test.js — 103/103 passed (3 new)
  • npx jest — 174/174 passed
  • End-to-end: built locally with npm link, ran confluence attachments <pageId> --download --dest /tmp/out against a live Atlassian Cloud page, confirmed the attachment downloaded successfully (byte-identical to the file fetched via curl directly against the REST API's _links.download).
  • End-to-end before the fix: same command against the same page returned Error: Request failed with status code 404, confirming the reproduction.

… downloads

The REST API returns _links.download as a path relative to the Confluence
context root (e.g. /download/attachments/<id>/<file>?version=...). On
Atlassian Cloud the context root is /wiki, so toAbsoluteUrl must prepend
webUrlPrefix before handing the URL to axios. Previously it went straight
through buildUrl, producing https://<domain>/download/... which returns
404 on Cloud. This made `confluence attachments --download` completely
unusable against any Cloud instance while listing still worked.

The fix reuses the existing webUrlPrefix (already set on line 38) and
guards against double-prefixing if the API ever starts returning paths
that already include /wiki. Server/DC behavior is unchanged because
webUrlPrefix is an empty string when apiPath does not start with /wiki/.

Adds three targeted tests covering Cloud download URL construction,
double-prefix guard, and Server/DC backward compatibility.
@adamtan945
Copy link
Copy Markdown
Author

Closing as duplicate of #93, which was opened earlier and has the same core fix. I didn't notice #93 when I opened this — apologies for the noise. I've moved my root cause analysis, end-to-end verification, and the three regression tests over to #93 so the review can happen in one place.

@adamtan945 adamtan945 closed this Apr 11, 2026
@adamtan945 adamtan945 deleted the fix/attachment-download-cloud-context-path branch April 11, 2026 14:04
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.

attachments --download fails with 404 on Atlassian Cloud (missing version/modificationDate/cacheVersion query params)

1 participant