Skip to content

fix: preserve same-space page links in Markdown#217

Merged
pchuri merged 9 commits into
mainfrom
codex/fix-same-space-page-links
Jul 15, 2026
Merged

fix: preserve same-space page links in Markdown#217
pchuri merged 9 commits into
mainfrom
codex/fix-same-space-page-links

Conversation

@pchuri

@pchuri pchuri commented Jul 15, 2026

Copy link
Copy Markdown
Owner

What Changed

  • Resolve same-space Confluence page links to absolute tenant URLs in Markdown reads and exports while preserving custom labels and inline formatting.
  • Harden link conversion for malformed storage, semantic macros, deep nesting, bounded lookups, and Markdown-safe text, code, datetime, and image labels.
  • Document the behavior and add regression coverage for page-link resolution and escaping.

Risk Assessment

✅ Low: The change is well-bounded, preserves fallback behavior, limits lookups, and includes focused regression coverage.

Testing

Reviewed the issue intent and diff, ran focused and full regression tests, then verified an actual CLI read→update round-trip against a Confluence-compatible HTTP fixture; the internal link remained intact and the worktree stayed clean. As this is CLI behavior, the transcript and captured API payload provide the reviewer-visible evidence rather than a screenshot.

Evidence: CLI read/update round-trip

Read emitted a tenant-qualified Markdown link; update persisted the same linked URL.

$ confluence read 123 --format markdown
Start here: [Target page](https://tenant.atlassian.net/wiki/spaces/ENG/pages/456/Target-page).

$ confluence update 123 --file same-space-read.md --format markdown
✅ Page updated successfully!
Title: Source page
ID: 123
Version: 8
URL: http://127.0.0.1:58616/spaces/ENG/pages/123/Source-page

Confluence-compatible API requests:
  GET /rest/api/content/123?expand=body.storage,space
  GET /rest/api/content?spaceKey=ENG&title=Target+page&limit=1
  GET /rest/api/content/123?expand=body.storage,version,space
  PUT /rest/api/content/123

Persisted storage body:
<p>Start here: <a href="https://tenant.atlassian.net/wiki/spaces/ENG/pages/456/Target-page" data-card-appearance="inline">Target page</a>.</p>


VERIFIED: the same-space page link is emitted as Markdown and remains linked in the update payload.
Evidence: Captured update payload
{
  "id": "123",
  "type": "page",
  "title": "Source page",
  "space": {
    "key": "ENG"
  },
  "body": {
    "storage": {
      "value": "<p>Start here: <a href=\"https://tenant.atlassian.net/wiki/spaces/ENG/pages/456/Target-page\" data-card-appearance=\"inline\">Target page</a>.</p>\n",
      "representation": "storage"
    }
  },
  "version": {
    "number": 8
  }
}
Evidence: CLI read output

Start here: [Target page](https://tenant.atlassian.net/wiki/spaces/ENG/pages/456/Target-page).

Start here: [Target page](https://tenant.atlassian.net/wiki/spaces/ENG/pages/456/Target-page).

Pipeline

Updates from git push no-mistakes

⏭️ **intent** - skipped

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 3 issues found → auto-fixed (7) ✅
  • 🚨 lib/confluence-client.js:668 - The resolver replaces every page link, including references inside include/shared-block macro parameters. This destroys the structure required by StorageWalker, causing macro output to disappear or lose semantics. Skip semantic macro-parameter links.
  • ⚠️ lib/confluence-client.js:715 - Fallback titles and custom bodies enter a Markdown link label without Markdown escaping. A crafted title such as evil](https://attacker) [x can inject another link, regressing the converter’s existing escaping invariant.
  • ⚠️ lib/confluence-client.js:697 - All unique link lookups run concurrently. Pages with many same-space links can create a large request burst, trigger rate limits, and silently leave links unresolved because lookup errors are swallowed. Apply bounded concurrency.

🔧 Fix: fix: harden same-space page link resolution
2 warnings still open:

  • ⚠️ lib/confluence-client.js:706 - Recursive DOM traversal bypasses StorageWalker’s depth guard. Pathologically nested storage can throw a native stack overflow before the controlled StorageDepthExceededError. Use iterative traversal or enforce the same depth limit.
  • ⚠️ lib/storage-walker.js:136 - Link-label escaping is context-blind and incomplete: plain *text* becomes unintended emphasis, while [x] inside &lt;code&gt; gains visible backslashes. Escape literal Markdown syntax outside code spans while preserving markup emitted by inline element handlers.

🔧 Fix: fix: harden deep page-link rendering
1 warning still open:

  • ⚠️ lib/storage-walker.js:183 - Code text bypasses escaping, but code spans always use one backtick. A label containing a backtick plus ]( can leave the span unmatched and inject a sibling Markdown link. Use a delimiter longer than the longest backtick run.

🔧 Fix: Harden inline code spans against Markdown link injection
1 warning still open:

  • ⚠️ lib/confluence-client.js:747 - Lenient parsing assigns EOF as endIndex for an unclosed &lt;ac:link&gt;, so replacement can delete the remainder of malformed storage and bypass StorageWalker’s warning path. Only replace explicitly closed link nodes.

🔧 Fix: Preserve malformed page links without dropping trailing content
1 warning still open:

  • ⚠️ lib/storage-walker.js:136 - Link-label escaping only covers text nodes. Attribute-derived inline content such as &lt;time datetime=&#34;x](https://attacker) [y&#34;&gt; bypasses escaping and can terminate a resolved page-link label. Escape every attribute-derived fragment emitted while _markdownLinkLabelDepth is active.

🔧 Fix: Escape datetime content in Markdown link labels
1 warning still open:

  • ⚠️ lib/confluence-client.js:451 - Only the space key reaches the resolver. Title-search result items omit _links.base, so scoped clients fall back to https://api.atlassian.com/wiki/... instead of the tenant URL. Pass the containing page’s _links.base through the lookup. See the Atlassian response examples.

🔧 Fix: Preserve tenant origins for scoped page links
1 warning still open:

  • ⚠️ lib/confluence-client.js:770 - Resolved links copy rich bodies into &lt;a&gt; unchanged. An embedded ac:image can emit an unescaped filename or URL that closes the Markdown label and injects another link. Make image rendering honor link-label escaping.

🔧 Fix: Escape images in resolved Markdown link labels
✅ Re-checked - no issues remain.

✅ **Test** - passed

✅ No issues found.

  • gh-axi issue view 215 --full
  • npm test -- --runInBand tests/confluence-client.test.js
  • node /var/folders/mm/mzvsb9xn40vd3pqkp4y8n_sm0000gn/T/no-mistakes-evidence/01KXJVGBVYCC6BB1P3KV9A7Y6T/cli-roundtrip-e2e.js
  • npm test -- --runInBand
  • git status --short --untracked-files=all
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

@pchuri pchuri self-assigned this Jul 15, 2026
@pchuri
pchuri merged commit d59a25a into main Jul 15, 2026
6 checks passed
@pchuri
pchuri deleted the codex/fix-same-space-page-links branch July 15, 2026 13:34
github-actions Bot pushed a commit that referenced this pull request Jul 15, 2026
## [2.16.2](v2.16.1...v2.16.2) (2026-07-15)

### Bug Fixes

* preserve same-space page links in Markdown ([#217](#217)) ([d59a25a](d59a25a)), closes [#215](#215)
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 2.16.2 🎉

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.

1 participant