Skip to content

feat(atlassian-tools): anchor-preserving Confluence page edits - #215

Open
coroiu wants to merge 3 commits into
mainfrom
confluence-anchor-preserving-edit
Open

feat(atlassian-tools): anchor-preserving Confluence page edits#215
coroiu wants to merge 3 commits into
mainfrom
confluence-anchor-preserving-edit

Conversation

@coroiu

@coroiu coroiu commented Aug 24, 2026

Copy link
Copy Markdown

🎟️ Tracking

No linked ticket — contributing a personal skill that has proven useful for editing Confluence pages without breaking open inline comments.

📔 Objective

Adds an ADF-native Confluence write path to bitwarden-atlassian-tools so pages can be edited without dropping the annotation marks that anchor open inline comments. Today the only way to update a page is updateConfluencePage with contentFormat: "markdown", whose markdown round-trip strips those marks and leaves every inline comment dangling with no highlight on the page. Editing as ADF keeps the mark (the anchor is the mark, not the text), so an anchored span keeps its comment even when its text changes.

The design mirrors the existing Jira write tools rather than introducing anything new: write is opt-in per install behind a token, tools are always listed, dry-run is the default, and a live write refuses without the token.

What's added

  • editing-confluence-pages skill — orchestrates the anchor-preserving workflow (baseline anchors → edit → verify).
  • replace_confluence_text (write, opt-in) — literal find/replace across text nodes, preserving marks. The low-bloat path for small edits; never routes the whole document through the conversation.
  • update_confluence_page (write, opt-in) — full ADF body push for larger/structural edits. Its dry run diffs the submitted body's anchors against the live page and warns about any that would be dropped.
  • get_confluence_page_adf, list_confluence_anchors (read-only) — fetch raw ADF + version, and list anchor ids and covered text.
  • Optional ATLASSIAN_CONFLUENCE_WRITE_TOKEN — opt-in per install, mirroring ATLASSIAN_JIRA_WRITE_TOKEN. The current body is always fetched with the read-only token; only the final write uses the write token.

Implementation notes

  • New tools live in the existing TypeScript MCP server; no bundled CLI. The shared write-guard helpers were parameterized (Jira defaults preserved byte-for-byte, all existing tests still green).
  • 27 new unit tests (ADF walk/replace/anchor logic, write-token auth mode, and each tool's dry-run / refusal / live-write path). Full suite: 377 passing.
  • plugin.json, marketplace.json, root README catalog, and CHANGELOG bumped to 2.7.0.

Validation run locally

  • pnpm build + pnpm test — 377 tests pass
  • prettier --check . — clean
  • cspell (full repo) — clean
  • gh-actions validate-plugin-structure.sh, validate-marketplace.sh, validate-version-bump.sh — all pass

🤖 Generated with Claude Code

Adds an ADF-native Confluence write path so pages can be edited without
dropping the annotation marks that anchor open inline comments — the failure
mode of the markdown-based updateConfluencePage round-trip.

New, gated exactly like the Jira write tools (always listed, dry-run by
default, live write refuses without the token):

- editing-confluence-pages skill orchestrating the anchor-preserving workflow
- replace_confluence_text (write): literal find/replace preserving marks
- update_confluence_page (write): full ADF body push, dry run diffs anchors
- get_confluence_page_adf, list_confluence_anchors (read-only helpers)
- optional ATLASSIAN_CONFLUENCE_WRITE_TOKEN, opt-in per install

Bumps the plugin to 2.7.0 with README, CHANGELOG, and marketplace updates.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coroiu
coroiu requested a review from a team as a code owner August 24, 2026 16:45
@github-actions

github-actions Bot commented Aug 24, 2026

Copy link
Copy Markdown

🤖 Bitwarden Claude Code Review

Overall Assessment: APPROVE

Reviewed the new ADF-native Confluence write path: the editing-confluence-pages skill, four new MCP tools, the atlas_doc_format fetch/push methods on ConfluenceClient, the ConfluenceAccessMode token split, and the parameterized write-guard helpers. The write gating mirrors the existing Jira pattern exactly — tools always listed, dry run by default, live write refused without ATLASSIAN_CONFLUENCE_WRITE_TOKEN, and the current body always fetched with the read-only token. Page ids are constrained to ^\d+$ before interpolation into the REST path, and Jira's existing guard wording is preserved byte-for-byte behind default parameters. Version bump, changelog, marketplace entry, and README catalog row are all present and consistent.

Code Review Details
  • ⚠️ : Full-body overwrite can silently clobber a concurrent edit — the version is refetched inside the handler, so Confluence's 409 conflict check never engages for the fetch-edit-push workflow
    • plugins/bitwarden-atlassian-tools/mcp/bitwarden-atlassian-mcp-server/src/tools/update-confluence-page.ts:99

@github-actions

github-actions Bot commented Aug 24, 2026

Copy link
Copy Markdown

Claude Code validation

Result: Pass

Validated the Claude material in PR #215 (feat(atlassian-tools): anchor-preserving Confluence page edits) against merge base c50ba9d, head 0e80626. One plugin changed (bitwarden-atlassian-tools, 2.6.0 → 2.7.0) with one new skill (editing-confluence-pages), its examples/ support file, an MCP server expansion, and the marketplace/manifest/README version bump. No CLAUDE.md, .claude/ settings, agents, commands, or hooks changed. No critical findings, no security weakening, no hardcoded credentials.

Critical

None.

Major

  • plugins/bitwarden-atlassian-tools/skills/editing-confluence-pages/SKILL.md:5allowed-tools no longer grants Read, so the progressive-disclosure pointer added at line 76 (See [examples/anchor_preserving_edit.md](examples/anchor_preserving_edit.md)) cannot be followed while the skill is active. Commit 0e80626 did both halves: it dropped Read from allowed-tools (present at 1ef409d) and added the example file plus the link to it. The result is that the skill's only reference file — the worked "keep the marks array" example the full-body workflow depends on — is unreachable at runtime. Sibling skill assessing-jira-issue-relevance grants Read for the same reason. Fix: re-add Read to the front of the allowed-tools list on line 5.

Minor

  • plugins/bitwarden-atlassian-tools/skills/editing-confluence-pages/SKILL.md:51 (also :55) — "Every edit tool takes a numeric pageId" and "the edit tools only accept a numeric pageId" describe the wrong JSON type. The schema is z.string().regex(/^\d+$/) (ConfluencePageId, mcp/bitwarden-atlassian-mcp-server/src/utils/validation.ts:169) and the tool inputSchema declares type: "string", so a model that emits pageId: 2923724969 as a JSON number fails validation. Fix: on both lines, say "a numeric-string pageId (e.g. "2923724969", quoted)", matching the wording the tool descriptions already use.
  • plugins/bitwarden-atlassian-tools/skills/editing-confluence-pages/SKILL.md:3-4description (697 chars) and the deprecated when_to_use (558 chars) now carry near-identical content: commit 0e80626 rewrote description to absorb the trigger list, the phrasings, and the do-not-use clause that when_to_use already stated, but left when_to_use in place verbatim. Current guidance is description-only; the duplication costs ~560 chars of always-loaded context and creates two places to keep in sync. Fix: delete line 4 and trim description toward the ~500-char guideline — keep the trigger phrasings and the "Do not use to create a page…" clause; the two-sentence explanation of annotation marks duplicates the body's "Why ADF, not markdown" section.
  • plugins/bitwarden-atlassian-tools/mcp/bitwarden-atlassian-mcp-server/src/tools/replace-confluence-text.ts:54 — the dropped-anchor diff in replace_confluence_text is tautological and can never report a drop, yet its "(all preserved)" output is presented to the model as a verified safety check. anchorsBefore (line 48) and anchorsAfter (line 54) are both collected from the same page.body object; replaceInTextNodes (utils/confluence-adf.ts:81) mutates only textNode.text and never marks, while collectInlineCommentAnchors derives ids exclusively from marks. So droppedAnchors is always empty and the ⚠️ would be dropped branches at lines 78-80 and 116-118 are unreachable; the spec at replace-confluence-text.spec.ts:105 only asserts the "all preserved" string, so nothing pins the branch. This matters because SKILL.md:66 tells the model to use the dry run to "confirm the match count and that no anchor would be dropped". Fix: either state the invariant directly (e.g. - **Inline-comment anchors:** N (marks are never modified by a text replacement)), or make the check real — deep-clone page.body before replaceInTextNodes, collect the baseline from the clone, and add a spec case exercising the drop branch. (The same diff in update_confluence_page is genuine, since it compares the live body against a caller-submitted body.)
  • plugins/bitwarden-atlassian-tools/skills/editing-confluence-pages/SKILL.md:5allowed-tools omits AskUserQuestion even though the skill mandates four user-confirmation gates (lines 12, 58, 67, 78), including the bolded "Show the dry-run output and get the user's approval before any dryRun: false call." Plain-text confirmation still works, so this is not broken, but the structured-prompt path is unavailable and assessing-jira-issue-relevance grants it for comparable confirmations. Fix: consider adding AskUserQuestion to line 5, particularly for the "show the candidates and confirm which one" branch on line 58.
  • plugins/bitwarden-atlassian-tools/skills/editing-confluence-pages/SKILL.md:95-97 — the "Known gotcha" section is a single ~110-word paragraph about a third-party MCP server's contentFormat enum: the densest, least-frequently-needed content in the file, reading as background justification rather than actionable procedure. Fix: optionally move to reference/atlassian-mcp-contentformat.md with a one-line pointer. At 1,198 body words there is no size pressure yet, so this is only worth doing if the file grows.

Notes

  • Verdict rationale. The major finding is a functional regression in the skill's own tool grant, not a security weakening — the grant narrowed rather than widened. Per the verdict rule (critical finding, security weakening, or failed script check), this reports as Pass with findings listed. The allowed-tools fix is still worth taking before merge.
  • Version bump and changelog satisfy repository policy. 2.6.0 → 2.7.0 is consistent across .claude-plugin/marketplace.json:51, plugins/bitwarden-atlassian-tools/.claude-plugin/plugin.json:3, and the root README.md:12 catalog table; this plugin has no agents/*/AGENT.md, so that requirement is vacuous. plugins/bitwarden-atlassian-tools/CHANGELOG.md:8-18 carries a Keep a Changelog ## [2.7.0] - 2026-08-24 entry under ### Added. MINOR is the correct level: four new MCP tools, one new skill, one new optional env var, all backward-compatible; no existing tool signature or env var changed.
  • Security posture of the new write path is sound. ATLASSIAN_CONFLUENCE_WRITE_TOKEN is added to .mcp.json:15 as an unexpanded ${VAR} reference, never a value, and src/confluence/auth.ts:13-19 treats a literal unexpanded ${...} as unset rather than authenticating with garbage. Write capability is opt-in per install; page fetches always use the read-only token (replace-confluence-text.ts:40, update-confluence-page.ts:218); both write tools default dryRun: true; and ConfluencePageId is regex-constrained to ^\d+$ before REST-path interpolation (utils/validation.ts:169-171), closing a path-traversal vector. Every credential-shaped string in the diff is an env var name, a documented placeholder (README.md:95), or an obvious test dummy ("write-token", "read-token").
  • Untrusted-content check: clean. SKILL.md and examples/anchor_preserving_edit.md were read as data. Both are ordinary runtime guidance scoped to editing Confluence pages; neither addresses a reviewer, claims repository authority, nor attempts to direct this validation. No CWE-1427 finding.

Checks run

Check Status
Plugin structure Skipped — run as a dedicated workflow step before this review; see the job log and check status
Marketplace Skipped — run as a dedicated workflow step before this review; see the job log and check status
Version bump Skipped — run as a dedicated workflow step before this review; version/changelog consistency independently confirmed by plugin validation (see Notes)
Plugin validation (AI) Passed — 1 minor finding (replace-confluence-text.ts:54)
Skill review (AI) Issues found — 1 major, 4 minor, all in editing-confluence-pages/SKILL.md
Configuration & security Passed — no findings. Scope was the skill support file examples/anchor_preserving_edit.md, .mcp.json, and .claude-plugin/plugin.json; no CLAUDE.md, .claude/ settings, agents, commands, or hooks.json changed, and no settings.local.json appears in the changeset. Enhanced secret detection skipped — bitwarden-security-engineer is not installed; the manual pattern scan ran as the fallback and found nothing.

Comment on lines +99 to +105
const updated = await writer.updatePage({
pageId: page.id,
title: page.title,
currentVersion: page.version,
adfBody: validated.adfBody,
message: validated.message ?? "update via update_confluence_page",
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ IMPORTANT: A full-body overwrite can silently clobber a concurrent edit — the documented version guard never engages here.

Details and fix

ConfluenceClient.updatePage's doc comment says the API "rejects with a conflict if the page changed underneath us." That holds only when the version travels with the body the caller edited. Here page.version comes from the refetch at line 41, a few milliseconds before the PUT, so currentVersion + 1 is always the correct next version and Confluence never returns a 409.

The workflow this tool is built for spans two tool calls with model editing in between: get_confluence_page_adf reports version N, the model rewrites the body, then update_confluence_page reads version M (possibly > N) and writes M+1 using a body derived from N. Anything a human changed in that window is overwritten with no warning. The anchor diff only surfaces it when the other edit happened to add or remove an inline comment.

One option — accept the version the body was fetched at and refuse on mismatch:

if (
  validated.expectedVersion !== undefined &&
  validated.expectedVersion !== page.version
) {
  return (
    `Refusing to overwrite: the page is now at version ${page.version}, but the ` +
    `submitted body was fetched at version ${validated.expectedVersion}. ` +
    "Re-fetch with get_confluence_page_adf and re-apply the edit."
  );
}

get_confluence_page_adf already prints the version, so the value is on hand.

replace_confluence_text is not affected — it reads, edits, and writes inside a single handler call.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good catch — fixed in 8f6d8a5.

update_confluence_page now takes an optional expectedVersion (the version the body was fetched at). When it's set and the live page has moved past it, the tool refuses the write with a re-fetch prompt instead of overwriting; the dry run surfaces the same mismatch. get_confluence_page_adf now prints the version to pass, and the editing-confluence-pages skill threads it through steps 4–5.

I kept it optional rather than required so the plain "just push a body" path and the dry-run preview still work without a token, but the fetch tool and skill both steer callers to pass it. replace_confluence_text is left as-is, since it reads/edits/writes in a single handler call. Added three tests covering match / stale-refusal / dry-run-warning.

coroiu and others added 2 commits August 25, 2026 09:19
The full-body overwrite spans two tool calls (get_confluence_page_adf →
model edits → update_confluence_page). The handler refetched the version
immediately before the PUT, so currentVersion+1 was always accepted and a
concurrent edit in the gap was silently clobbered — the documented version
guard never engaged.

Add an optional expectedVersion parameter (the version the body was fetched
at). When set and the live page has moved, the write is refused with a
re-fetch prompt; the dry run surfaces the same mismatch. get_confluence_page_adf
now emits the version to pass, and the skill threads it through. replace_confluence_text
is unaffected (single-call read-edit-write).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Applies the automated skill review's findings, all in the new skill:

- description now stands alone for dispatch: folds in trigger phrasings and
  the "do not use" exclusions, and drops the unverified "layouts" claim
- add search_confluence / search_confluence_cql to allowed-tools and a
  page-id resolution step, so the skill can act on a URL or title (all edit
  tools require a numeric pageId); drop unused Read grant
- unconditional approval gate before any dryRun:false write, as a numbered
  step in both workflows, matching filing-jira-tickets
- fix the "big rewrite" off-ramp that routed comment-bearing pages down the
  anchor-destroying markdown path; describe the external tool by capability
- known-gotcha now states what these tools add over external contentFormat:adf
- add examples/anchor_preserving_edit.md (before/after + the deletion mistake)
  and link it from the edit step; consistent external-tool naming

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

coroiu commented Aug 25, 2026

Copy link
Copy Markdown
Author

Thanks — addressed all of the skill review's findings in 0e80626. Summary:

Major

  • Over-triggering description — folded the trigger phrasings and the "do not use" exclusions into description so it stands alone for dispatch (kept when_to_use for repo consistency).
  • No page-id resolution — added search_confluence / search_confluence_cql to allowed-tools and a "Resolving the page id" step (URL or title) as step 1 of both workflows, so the skill can act on its own trigger phrasings.
  • No approval gate — replaced the conditional "confirm when anchors are fragile" with an unconditional gate ("show the dry-run output and get approval before any dryRun: false, unless told to skip"), and made it a numbered step in both workflows.
  • Contradictory "big rewrite" off-ramp — a large rewrite of a page with inline comments now explicitly stays in this skill; the markdown off-ramp is qualified to pages with no inline comments.

Minor

  • External tool described by capability (and noted this skill doesn't grant it), consistent naming, first mention qualified with "if that plugin is installed".
  • Known-gotcha now says what these tools add over external contentFormat: "adf" (anchor baseline/diff, expectedVersion guard, no full-document round-trip).
  • Narrowed the description's "layouts, and other ADF features" claim to anchors.
  • Dropped the unused Read grant.
  • Added examples/anchor_preserving_edit.md (correct before/after plus the drop-the-mark and delete-the-node mistakes) and linked it from the edit step.

Re-ran locally: plugin-structure validator passes (the added tools are registered), prettier --check and cspell clean, 380 tests still green.

@coroiu

coroiu commented Aug 25, 2026

Copy link
Copy Markdown
Author

@claude re-review

@claude

claude Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Claude finished @coroiu's task in 3m 26s —— View job


Re-review

Re-read the full diff against c50ba9d, focused on the two follow-up commits (8f6d8a5 version guard, 0e80626 skill review). Overall this is in good shape — the lost-update fix is correct and well-tested, and the skill rewrite addresses the earlier findings. Three things left, one of which is a real behavioral gap.

Previous findings — verified fixed

  • Lost update on full-body overwrite ✅ — expectedVersion is threaded end-to-end: schema (validation.ts:212), refusal before the write (update-confluence-page.ts:121-128, correctly placed before the token check so a stale write can't slip through), dry-run mismatch warning (:82-98), and get_confluence_page_adf now prints the version to pass (get-confluence-page-adf.ts:34). Three tests cover match / stale-refusal / dry-run-warning. replace_confluence_text is genuinely exempt — its read-edit-write is one handler call, and updatePage sends currentVersion + 1, so a real 409 does engage there.
  • Skill: page-id resolution, unconditional approval gate, description standing on its own, the "big rewrite" off-ramp ✅ — all present and consistent between the two workflows (SKILL.md:53-79). Keeping when_to_use is fine; it's the repo-wide convention.
  • Version/changelog policy ✅ — 2.7.0 in marketplace.json:51, plugin.json:3, root README.md:12, plus a Keep a Changelog entry. MINOR is right for four additive tools + one skill + one optional env var.
  • Write gating ✅ — hasConfluenceWriteToken short-circuits before new ConfluenceClient("write") in both tools, the body fetch always uses the read token, ConfluencePageId stays regex-pinned to ^\d+$ before REST-path interpolation, and the parameterized write-guard helpers keep Jira's wording byte-for-byte behind defaults. isWriteAuthError(msg, "Confluence") matches the client's actual 401 text (client.ts:70).

Remaining

  1. ⚠️ Empty newText can produce an invalid ADF text node — and the dry run calls it safe. replace-confluence-text.ts:49-53. newText: "" is a documented use ("May be empty to delete the matched text"), but emptying a node's whole text leaves {"type":"text","text":"","marks":[…]}, which ADF disallows. On the pages this tool targets the anchored phrase is its own text node, so "delete this phrase" hits it directly: the preview says 1 occurrence, anchors 1 before, 1 after (all preserved), and the write then 400s or lands with the mark gone. Details inline · Fix this →

  2. 🟡 The anchor diff in replace_confluence_text is still tautological (raised in the last pass, unchanged). anchorsBefore (:48) and anchorsAfter (:54) are both read from the same page.body object, and replaceInTextNodes never touches marks — so droppedAnchors is always empty and the ⚠️ would be dropped branches at :78-80 and :116-118 are dead. SKILL.md:66 tells the model to use this output to "confirm … that no anchor would be dropped", so it reads as a verified check that never ran. Fixing (1) as suggested makes the diff real; otherwise state the invariant instead (marks are never modified by a text replacement) rather than presenting an empty diff as evidence.

  3. 🟡 allowed-tools dropped Read, but the same commit added a linked example. SKILL.md:5 vs the pointer at :76 to examples/anchor_preserving_edit.md. The example is good and the workflow step leans on it; it just isn't reachable while the skill is active. Sibling filing-jira-tickets grants Read. Suggestion inline

Nit (non-blocking)

  • replaceInTextNodes only matches within a single text node, so a phrase that straddles an annotation or formatting boundary silently returns no match — likeliest on precisely the commented pages this targets. The no-match message (replace-confluence-text.ts:59-66) advises checking whitespace and case; worth adding that marks split a sentence into several text nodes, so a phrase overlapping an anchor boundary needs the full-body path.

Not verified

I couldn't run pnpm install/pnpm build/pnpm test in this environment (no network for the install step), so the 380-test claim rests on your local run and CI, not on anything I executed. Static review of the new specs looks sound — the version-guard and anchor-drop paths are covered; the drop branches in replace_confluence_text are the ones nothing pins.
• branch confluence-anchor-preserving-edit

Comment on lines +49 to +53
const result = replaceInTextNodes(
page.body,
validated.oldText,
validated.newText,
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ IMPORTANT: An empty newText can produce an invalid ADF text node, and the dry run reports it as safe.

Details and fix

newText is documented as "May be empty to delete the matched text" (line 158). When oldText is the whole of a node's text — the common case on exactly the pages this tool targets, since an annotation mark makes the anchored phrase its own text node — replaceInTextNodes leaves { "type": "text", "text": "", "marks": [...] } behind. ADF requires a text node's text to be non-empty, so the PUT either comes back as a 400 or the node is stripped, taking its annotation mark with it.

Concretely: "delete the phrase exportItems() from the page", where that phrase carries anchor a1b2c3. The dry run reports 1 occurrence(s) across 1 text node(s) and Inline-comment anchors: 1 before, 1 after (all preserved), the user approves, and the live write fails — or lands and drops the anchor. Neither outcome is what the preview promised.

Worth handling explicitly in replaceInTextNodes/the handler: after the replace, find text nodes whose text is now "", prune them from their parent's content, and let the existing anchor diff report the anchors that go with them (which also makes the diff below non-tautological). Refusing with a clear message when a replacement would empty an anchored node would be fine too — the point is that the preview and the write agree.

name: editing-confluence-pages
description: Edit a Confluence page without breaking its open inline comments. Confluence anchors each inline comment to a text node with an annotation mark that a markdown round-trip strips, leaving the comment dangling; this skill edits through ADF so the anchor survives even when the text changes. Use for a small surgical edit (a typo, a renamed symbol, a reworded phrase) or for any edit to a page that has open inline comments — phrasings like "fix this on the Confluence page", "update the doc but keep the comments", "resolve comment 3 by changing the text", "preserve the anchors". Do not use to create a page, or to rewrite a page that has no inline comments (a plain markdown update is fine there).
when_to_use: Use when editing a Confluence page and any of these hold — the page has open inline comments that must stay attached after the edit, the user asks to preserve anchors/comments, or the edit is a small surgical change (a typo, a renamed symbol, a reworded phrase). Phrasings like "fix this on the Confluence page", "update the doc but keep the comments", "resolve comment 3 by changing the text", "preserve the anchors". Do not use for creating a fresh page, or for a large rewrite of a page that has no inline comments (a plain markdown update is fine there).
allowed-tools: mcp__plugin_bitwarden-atlassian-tools_bitwarden-atlassian__search_confluence, mcp__plugin_bitwarden-atlassian-tools_bitwarden-atlassian__search_confluence_cql, mcp__plugin_bitwarden-atlassian-tools_bitwarden-atlassian__list_confluence_anchors, mcp__plugin_bitwarden-atlassian-tools_bitwarden-atlassian__get_confluence_page_adf, mcp__plugin_bitwarden-atlassian-tools_bitwarden-atlassian__replace_confluence_text, mcp__plugin_bitwarden-atlassian-tools_bitwarden-atlassian__update_confluence_page

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 SUGGESTED: allowed-tools has no Read, so the example added in the same commit can't be opened.

Step 4 of the full-body workflow (line 76) points at examples/anchor_preserving_edit.md — the worked "keep the marks array" example that step depends on — but with allowed-tools limited to the six MCP tools, the skill can't read it while active. Sibling filing-jira-tickets grants Read for the same reason. Adding Read to the front of the list restores the pointer:

Suggested change
allowed-tools: mcp__plugin_bitwarden-atlassian-tools_bitwarden-atlassian__search_confluence, mcp__plugin_bitwarden-atlassian-tools_bitwarden-atlassian__search_confluence_cql, mcp__plugin_bitwarden-atlassian-tools_bitwarden-atlassian__list_confluence_anchors, mcp__plugin_bitwarden-atlassian-tools_bitwarden-atlassian__get_confluence_page_adf, mcp__plugin_bitwarden-atlassian-tools_bitwarden-atlassian__replace_confluence_text, mcp__plugin_bitwarden-atlassian-tools_bitwarden-atlassian__update_confluence_page
allowed-tools: Read, mcp__plugin_bitwarden-atlassian-tools_bitwarden-atlassian__search_confluence, mcp__plugin_bitwarden-atlassian-tools_bitwarden-atlassian__search_confluence_cql, mcp__plugin_bitwarden-atlassian-tools_bitwarden-atlassian__list_confluence_anchors, mcp__plugin_bitwarden-atlassian-tools_bitwarden-atlassian__get_confluence_page_adf, mcp__plugin_bitwarden-atlassian-tools_bitwarden-atlassian__replace_confluence_text, mcp__plugin_bitwarden-atlassian-tools_bitwarden-atlassian__update_confluence_page

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.

1 participant