Skip to content

feat(cfl): add page export to PDF - #481

Merged
piekstra merged 9 commits into
mainfrom
piekstra/cfl-page-export-pdf
Oct 1, 2026
Merged

piekstra merged 9 commits into
mainfrom
piekstra/cfl-page-export-pdf

Conversation

@piekstra

@piekstra piekstra commented Aug 19, 2026 •

Copy link
Copy Markdown
Member

Summary

Adds cfl page export <page-id>, which writes a Confluence page out as a PDF.

Everything else about a page is reachable from the CLI, but exporting one was
not, so any workflow that ends in "send this page to someone as a document" had
to stop and go click Export to PDF in the web UI. This closes that gap.

cfl page export 123456                          # filename from the page title
cfl page export 123456 -O handoff.pdf
cfl page export 123456 -O handoff.pdf --force
cfl page export 123456 --timeout 10m
Exporting: 0% complete
Exported: Quarterly Handoff.pdf
Size: 255.6 KB

Why it works the way it does

Confluence Cloud publishes no REST endpoint for PDF export. The request has
been open since 2018 (CONFCLOUD-61557,
still Gathering Interest), and the Atlassian KB article that describes a
302 straight to a PDF is scoped to Data Center and does not apply here. The
v1 and v2 API groups have no export surface at all. I confirmed both against
a live Cloud site rather than taking the docs' word for it:

Probe Result
GET /wiki/api/v2/pages/{id}/export 404
GET /wiki/rest/api/content/{id}/export 404
GET /wiki/rest/api/content/{id}/exportpdf 404

What does exist is the flow the Confluence UI itself uses, and it works with the
API-token auth cfl already has. It has three legs:

  1. GET /wiki/spaces/flyingpdf/pdfpageexport.action?pageId=<id> starts a
    server-side render and returns an HTML progress page. The task identifier is
    published only as <meta name="ajs-taskId"> inside that page, which is why
    this leg parses HTML. A sibling ajs-isV3 declares which progress endpoint
    served the request.
  2. GET /wiki/api/v2/pdfexporttask/progress/<taskId> reports
    {progress, state, result} until state reaches SUCCEEDED.
  3. result names the rendered document, which is then downloaded.

Four behaviors here are not obvious and each one is load-bearing:

  • The XSRF header decides the request. Without X-Atlassian-Token: no-check
    the start request is refused with 403; with it, 200. I hit exactly this
    while building it.
  • A recognized route and a missing one both return the SPA shell, so status
    is the only thing that separates them. A bogus action path returns 404 while
    pdfpageexport.action returns 200, which is the control that establishes the
    route is real rather than being swallowed by the SPA router.
  • The result URL may be absolute or site-relative, and the auth handling
    inverts between them.
    The document is normally handed to a media host with a
    signed URL carrying its own access, where sending the Atlassian credential is
    both wrong and a credential leak to another host. A site-relative result needs
    the credential. Sending it is therefore decided by where the URL points.
  • A rejected credential is answered with a sign-in page under HTTP 200. The
    status code cannot catch that, so the download is verified to start with
    %PDF- and fails with ErrExportNotPDF otherwise, rather than writing an HTML
    page to disk under a .pdf name.

Both progress-endpoint generations are supported because the page declares which
one applies; the pre-v3 shape adds one indirection (the result addresses a
resource whose body is the document URL). Neither shape is guessed: both come
from the export script Confluence itself ships.

Verification

Run against a live Confluence Cloud site with the ordinary API-token config:

$ cfl page export <page-id>
Exporting: 0% complete
Exported: <page title>.pdf
Size: 255.6 KB

$ file '<page title>.pdf'
PDF document, version 1.4, 3 pages

Guard paths, also run live:

$ cfl page export <page-id> --format docx
Error: invalid export format: "docx" (valid formats: pdf)

$ cfl page export <page-id>
Error: file already exists: <page title>.pdf (use --force to overwrite)

$ cfl page export 999999999
Error: starting export: page 999999999 not found, or not visible to this user

$ cfl page export <page-id> --timeout 0
Error: invalid --timeout: 0s (must be greater than zero)

Re-run on 2026-09-30 after merging current main, built from the branch head, against a live Cloud site with API-token auth: a small page exported in about 11 seconds to a 173.3 KB file that starts with %PDF-1.4 and that pdfinfo reads as 3 pages. The existing-file guard refused a second run and --force overwrote it.

make check is green (tidy, lint, test, build). go test -race ./... and golangci-lint run ./... in tools/cfl also pass on the merged head (0 lint issues).

Notes for review

  • The flyingpdf path is undocumented and Atlassian has moved it before (the
    progress endpoint changed in April 2026, breaking the recipes in the wild).
    That is an argument for the failure modes above being explicit rather than for
    avoiding the path, since it is the only one that produces a PDF at all. The
    alternative considered was rendering the page body locally, which would produce
    a document that does not match what Confluence's own export produces.
  • Output follows the attachment download precedent (-O/--output-file,
    -f/--force) and the mutation contract in OUTPUT_SPEC.md: a success summary
    plus follow-up fields, progress on stderr so stdout stays clean for scripting.
  • --format currently accepts only pdf. It is there so the closed set is
    stated and rejected loudly rather than a second format being bolted on later
    by positional convention.
  • Wire fixtures are synthetic and live in api/testdata/.

Docs

tools/cfl/README.md, root README.md, internal/cmd/OUTPUT_SPEC.md,
skills/Confluence/CliReference.md, skills/Confluence/Workflows/ManagePage.md,
and the cfl changelog.

tools/cfl/integration-tests.md gains a page export section. That catalog is
where a break in the undocumented flow would actually be caught, and it also
records the one thing I could not establish: bearer auth is unverified for
this command.
The export goes through a web action rather than a REST
endpoint, and whether the api.atlassian.com gateway proxies that action has
not been tested. I would rather state that than let it be assumed.

Exporting a page to PDF was the one thing that sent a workflow back to the
Confluence web UI, which matters when the page is a document that has to be
sent to someone outside the site.

Confluence Cloud publishes no REST endpoint for this. What it does have is
the action its own UI navigates to, which both starts a server-side render
and returns a page carrying the task identifier as metadata, plus a progress
endpoint that reports the task and finally names the rendered document.
`page export` drives those three legs: start, poll, download.

Details worth knowing at the call site:

- The XSRF header is what separates an accepted start request from a 403.
- Confluence declares which generation of the progress endpoint served the
  request, and that also decides whether the task result is the document URL
  or a URL yielding one. Both are handled; neither is guessed.
- The document is handed off to a media host with a signed URL that carries
  its own access, so the credential is sent only to the configured site and
  never to whatever host the task names.
- A rejected credential is answered with a sign-in page under HTTP 200, so
  the download is checked for the PDF header rather than trusting the status.

Progress goes to stderr and the success block to stdout, so redirecting
stdout captures the result and nothing else. Without --output-file the page
title names the file, reduced to a single path element.
The export drives an undocumented Confluence flow that Atlassian has moved
before without notice, so the catalog is where a regression in it would be
caught. Also records that bearer auth is unverified for this command rather
than leaving the gap implied: the export goes through a web action rather
than a REST endpoint, and whether the gateway proxies that action has not
been established.

@monit-reviewer monit-reviewer 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: d53ff2b59a0e
Profile: claude-monit-reviewer - Posting as: monit-reviewer

Summary

Reviewer Findings
go:implementation-tests 1
policies:conventions 0
security:code-auditor 0
architecture:solid-reviewer-agnostic 2
go:implementation-tests (1 finding)

Minor - tools/cfl/internal/cmd/page/export.go:206

writeExport creates the output file and then streams the download into it with io.Copy, but if the reader errors partway through (a dropped connection while a large PDF renders and downloads), the partially written file is left on disk at outputPath with no cleanup. A retry without --force then fails with "file already exists" on a corrupt, truncated PDF rather than the partial file being removed so the user can simply rerun the command. None of the export_test.go cases exercise a reader that fails mid-copy, so this path is both unhandled and untested. Fix: on io.Copy error, remove the partially written file (os.Remove(outputPath)) before returning the error, and add a test using a reader that fails after writing some bytes to confirm the file does not survive a failed export.

architecture:solid-reviewer-agnostic (2 findings)

Minor - tools/cfl/api/export.go:138

U-L2: StartPDFExport turns HTTP status codes into untyped strings (page %s not found, or not visible to this user, status %d), but the progress leg goes through the shared client's Get, which returns typed errors from sharederrors.ParseAPIError (ErrNotFound, ErrUnauthorized, ErrForbidden, ...). The result is that one command returns two different error kinds. A caller can match errors.Is(err, ErrForbidden) when polling is refused, but it cannot when the start request is refused with 403. That 403 is exactly the XSRF failure the PR description calls load-bearing. Fix: keep the user-facing text but wrap the sentinel, e.g. fmt.Errorf("starting export: page %s not found, or not visible to this user: %w", pageID, sharederrors.ErrNotFound). For other non-200 statuses, return sharederrors.ParseAPIError(resp.StatusCode, body) wrapped with %w. Non-blocking because attachments.DownloadAttachment already uses untyped status strings, so this diff follows an existing precedent. It does add a second instance of that pattern.

Minor - tools/cfl/internal/cmd/page/export.go:24

U-D1: the poll cadence is a package const that awaitExport reads ambiently through time.After(pollInterval). Tests cannot change it, so any test where the task does not finish on the first read waits a real 2 seconds per poll. TestRunExport_ReportsProgress already pays this cost: the page package tests take about 3.1s. It also makes multi-step progress cases, such as de-duplicating repeated percentages or running several steps before success, too slow to add. Fix: put the interval on exportOptions (default pollInterval, which tests set to milliseconds) or pass it to awaitExport as a parameter. This matches how timeout is already injected.

Reviewer Coverage

  • go:implementation-tests — complete (broad); inspected 7 assigned files (17 inspected across reviewers): tools/cfl/api/export.go, tools/cfl/api/export_test.go, tools/cfl/internal/cmd/page/export.go, tools/cfl/internal/cmd/page/export_test.go, tools/cfl/internal/cmd/page/page.go, tools/cfl/internal/present/mutation.go, tools/cfl/internal/present/mutation_test.go; skipped: none; constraints: none
  • policies:conventions — complete (broad); inspected 5 assigned files (17 inspected across reviewers): README.md, tools/cfl/README.md, tools/cfl/internal/cmd/OUTPUT_SPEC.md, tools/cfl/internal/cmd/page/export.go, tools/cfl/internal/cmd/page/page.go; skipped: none; constraints: No sibling ../cli-common or ../.github checkouts were present, so family-wide Open CLI Collective standards (command-surface.md, output-and-rendering.md, repo-layout.md) could not be checked directly; review relied on repo-local docs and existing command precedent (attachment/download.go) instead.
  • security:code-auditor — complete (broad); inspected 9 assigned files (17 inspected across reviewers): skills/Confluence/CliReference.md, skills/Confluence/Workflows/ManagePage.md, tools/cfl/CHANGELOG.md, tools/cfl/api/export.go, tools/cfl/api/testdata/pdf_export_progress_running.json, tools/cfl/api/testdata/pdf_export_progress_succeeded.json, tools/cfl/api/testdata/pdf_export_start.html, tools/cfl/integration-tests.md, tools/cfl/internal/cmd/page/export.go; skipped: none; constraints: none
  • architecture:solid-reviewer-agnostic — complete (broad); inspected 4 assigned files (17 inspected across reviewers): tools/cfl/api/export.go, tools/cfl/internal/cmd/page/export.go, tools/cfl/internal/cmd/page/page.go, tools/cfl/internal/present/mutation.go; skipped: none; constraints: Ran go test for api, internal/cmd/page and internal/present in tools/cfl (all pass) and go vet ./... (clean); did not run full make check or golangci-lint.
Inspected files (17)
  • README.md
  • skills/Confluence/CliReference.md
  • skills/Confluence/Workflows/ManagePage.md
  • tools/cfl/CHANGELOG.md
  • tools/cfl/README.md
  • tools/cfl/api/export.go
  • tools/cfl/api/export_test.go
  • tools/cfl/api/testdata/pdf_export_progress_running.json
  • tools/cfl/api/testdata/pdf_export_progress_succeeded.json
  • tools/cfl/api/testdata/pdf_export_start.html
  • tools/cfl/integration-tests.md
  • tools/cfl/internal/cmd/OUTPUT_SPEC.md
  • tools/cfl/internal/cmd/page/export.go
  • tools/cfl/internal/cmd/page/export_test.go
  • tools/cfl/internal/cmd/page/page.go
  • tools/cfl/internal/present/mutation.go
  • tools/cfl/internal/present/mutation_test.go

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


Completed in 4m 29s | ~$2.46 (est.) | claude-sonnet-5, claude-opus-5-5 | cr 0.10.318
Field Value
Model claude-sonnet-5, claude-opus-5-5
Reviewers go:implementation-tests, policies:conventions, security:code-auditor, architecture:solid-reviewer-agnostic
Engine claude_cli · claude-sonnet-5, claude-opus-5-5
Reviewed by cr · monit-reviewer
Duration 4m 29s wall · 7m 12s compute
Cost ~$2.46 (est.)
Pricing basis anthropic-public-2026-09-23
Tokens 94 in / 25.1k out

Per-workstream usage

  • orchestrator-selection — claude-sonnet-5
    • In: 4
    • Out: 4.9k
    • Cache read: 34.5k
    • Cache create: 41.9k
    • Cost: ~$0.22 (est.)
    • Duration: 46s
  • go:implementation-tests — claude-sonnet-5
    • In: 22
    • Out: 5.7k
    • Cache read: 668.2k
    • Cache create: 68.4k
    • Cost: ~$0.46 (est.)
    • Duration: 1m 38s
  • policies:conventions — claude-sonnet-5
    • In: 26
    • Out: 4.1k
    • Cache read: 799.3k
    • Cache create: 61.8k
    • Cost: ~$0.45 (est.)
    • Duration: 1m 34s
  • security:code-auditor — claude-sonnet-5
    • In: 16
    • Out: 5.5k
    • Cache read: 432.6k
    • Cache create: 56.4k
    • Cost: ~$0.37 (est.)
    • Duration: 1m 34s
  • architecture:solid-reviewer-agnostic — claude-opus-5-5
    • In: 22
    • Out: 4.3k
    • Cache read: 560.7k
    • Cache create: 72.4k
    • Cost: ~$0.78 (est.)
    • Duration: 1m 21s
  • orchestrator-rollup — claude-sonnet-5
    • In: 4
    • Out: 557
    • Cache read: 87.1k
    • Cache create: 39.8k
    • Cost: ~$0.18 (est.)
    • Duration: 17s

Comment thread tools/cfl/api/export.go
Comment thread tools/cfl/internal/cmd/page/export.go Outdated
Comment thread tools/cfl/internal/cmd/page/export.go Outdated
The start leg of page export now maps error statuses through the shared
API error parser, so a missing page reports ErrNotFound and a refused
request ErrForbidden, the same as the progress leg and the rest of the
client.
The document is streamed to a temporary file beside the destination and
renamed into place only after the download completes, so a download cut
off midway leaves no truncated file and an existing file replaced with
--force survives a failed attempt.

The progress poll interval is now a package variable that tests shorten,
so polling tests no longer wait in real time. Faster polling exposed that
a deadline lapsing during a progress read lost the --timeout hint; both
paths now report it.

Export fixtures for the command tests move to testdata, and a missing
page no longer repeats the 'getting page' prefix.

@monit-reviewer monit-reviewer 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: 4a09a391b5c0
Profile: claude-monit-reviewer - Posting as: monit-reviewer

Summary

Reviewer Findings
go:implementation-tests 0
policies:conventions 0
security:code-auditor 0
architecture:solid-reviewer-agnostic 0

Reviewer Coverage

  • go:implementation-tests — complete (constrained); inspected 7 assigned files (25 inspected across reviewers): tools/cfl/api/export.go, tools/cfl/api/export_test.go, tools/cfl/internal/cmd/page/export.go, tools/cfl/internal/cmd/page/export_test.go, tools/cfl/internal/cmd/page/page.go, tools/cfl/internal/present/mutation.go, tools/cfl/internal/present/mutation_test.go; skipped: none; constraints: none
  • policies:conventions — complete (constrained); inspected 5 assigned files (25 inspected across reviewers): README.md, tools/cfl/README.md, tools/cfl/internal/cmd/OUTPUT_SPEC.md, tools/cfl/internal/cmd/page/export.go, tools/cfl/internal/cmd/page/page.go; skipped: none; constraints: No sibling ../cli-common or ../.github checkouts were present, so family-wide Open CLI Collective standards (command-surface.md, output-and-rendering.md, repo-layout.md) could not be checked directly; review relied on repo-local docs and existing command precedent (attachment/download.go) instead.
  • security:code-auditor — complete (constrained); inspected 17 assigned files (25 inspected across reviewers): skills/Confluence/CliReference.md, skills/Confluence/Workflows/ManagePage.md, tools/cfl/CHANGELOG.md, tools/cfl/api/export.go, tools/cfl/api/testdata/pdf_export_progress_running.json, tools/cfl/api/testdata/pdf_export_progress_succeeded.json, tools/cfl/api/testdata/pdf_export_start.html, tools/cfl/api/testdata/pdf_export_start_forbidden.html, tools/cfl/api/testdata/pdf_export_start_not_found.html, tools/cfl/integration-tests.md, tools/cfl/internal/cmd/page/export.go, tools/cfl/internal/cmd/page/testdata/export_document.pdf, tools/cfl/internal/cmd/page/testdata/export_page.json, tools/cfl/internal/cmd/page/testdata/export_progress_failed.json, tools/cfl/internal/cmd/page/testdata/export_progress_running.json, tools/cfl/internal/cmd/page/testdata/export_progress_succeeded.json, tools/cfl/internal/cmd/page/testdata/export_start.html; skipped: none; constraints: none
  • architecture:solid-reviewer-agnostic — complete (constrained); inspected 4 assigned files (25 inspected across reviewers): tools/cfl/api/export.go, tools/cfl/internal/cmd/page/export.go, tools/cfl/internal/cmd/page/page.go, tools/cfl/internal/present/mutation.go; skipped: none; constraints: Ran go test -count=1 for api, internal/cmd/page and internal/present with CGO_ENABLED=0 (all pass). -race could not run because the sandbox's clang module-cache path contains a space; make check and golangci-lint were not run. The earlier findings on typed start errors (U-L2) and the injectable poll interval (U-D1) are settled threads. I verified both fixes in 065286a and 4a09a39 and did not raise them again.
Inspected files (25)
  • README.md
  • skills/Confluence/CliReference.md
  • skills/Confluence/Workflows/ManagePage.md
  • tools/cfl/CHANGELOG.md
  • tools/cfl/README.md
  • tools/cfl/api/export.go
  • tools/cfl/api/export_test.go
  • tools/cfl/api/testdata/pdf_export_progress_running.json
  • tools/cfl/api/testdata/pdf_export_progress_succeeded.json
  • tools/cfl/api/testdata/pdf_export_start.html
  • tools/cfl/api/testdata/pdf_export_start_forbidden.html
  • tools/cfl/api/testdata/pdf_export_start_not_found.html
  • tools/cfl/integration-tests.md
  • tools/cfl/internal/cmd/OUTPUT_SPEC.md
  • tools/cfl/internal/cmd/page/export.go
  • tools/cfl/internal/cmd/page/export_test.go
  • tools/cfl/internal/cmd/page/page.go
  • tools/cfl/internal/cmd/page/testdata/export_document.pdf
  • tools/cfl/internal/cmd/page/testdata/export_page.json
  • tools/cfl/internal/cmd/page/testdata/export_progress_failed.json
  • tools/cfl/internal/cmd/page/testdata/export_progress_running.json
  • tools/cfl/internal/cmd/page/testdata/export_progress_succeeded.json
  • tools/cfl/internal/cmd/page/testdata/export_start.html
  • tools/cfl/internal/present/mutation.go
  • tools/cfl/internal/present/mutation_test.go

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


Completed in 1m 56s | ~$1.56 (est.) | claude-sonnet-5, claude-opus-5-5 | cr 0.10.318
Field Value
Model claude-sonnet-5, claude-opus-5-5
Reviewers go:implementation-tests, policies:conventions, security:code-auditor, architecture:solid-reviewer-agnostic
Engine claude_cli · claude-sonnet-5, claude-opus-5-5
Reviewed by cr · monit-reviewer
Duration 1m 56s wall · 3m 37s compute
Cost ~$1.56 (est.)
Pricing basis anthropic-public-2026-09-23
Tokens 54 in / 8.9k out

Per-workstream usage

  • go:implementation-tests — claude-sonnet-5
    • In: 10
    • Out: 1.5k
    • Cache read: 429.0k
    • Cache create: 79.0k
    • Cost: ~$0.42 (est.)
    • Duration: 39s
  • policies:conventions — claude-sonnet-5
    • In: 8
    • Out: 1.8k
    • Cache read: 296.0k
    • Cache create: 66.9k
    • Cost: ~$0.35 (est.)
    • Duration: 32s
  • security:code-auditor — claude-sonnet-5
    • In: 10
    • Out: 2.2k
    • Cache read: 388.5k
    • Cache create: 68.6k
    • Cost: ~$0.37 (est.)
    • Duration: 42s
  • architecture:solid-reviewer-agnostic — claude-opus-5-5
    • In: 20
    • Out: 3.0k
    • Cache read: 784.9k
    • Cache create: 13.7k
    • Cost: ~$0.33 (est.)
    • Duration: 1m 22s
  • orchestrator-rollup — claude-sonnet-5
    • In: 6
    • Out: 431
    • Cache read: 200.7k
    • Cache create: 12.3k
    • Cost: ~$0.09 (est.)
    • Duration: 20s

The temporary file behind the atomic write was created with
os.CreateTemp, which fixes its mode at 0600. It is now opened with
O_EXCL and mode 0666 under a random name beside the destination, retried
a bounded number of times on a taken name, so the finished document gets
the same permissions os.Create gives an attachment download.

@monit-reviewer monit-reviewer 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: 58f3d1209710
Profile: claude-monit-reviewer - Posting as: monit-reviewer

Summary

Reviewer Findings
go:implementation-tests 0
policies:conventions 0
security:code-auditor 0
architecture:solid-reviewer-agnostic 0

Reviewer Coverage

  • go:implementation-tests — complete (constrained); inspected 7 assigned files (25 inspected across reviewers): tools/cfl/api/export.go, tools/cfl/api/export_test.go, tools/cfl/internal/cmd/page/export.go, tools/cfl/internal/cmd/page/export_test.go, tools/cfl/internal/cmd/page/page.go, tools/cfl/internal/present/mutation.go, tools/cfl/internal/present/mutation_test.go; skipped: none; constraints: none
  • policies:conventions — complete (constrained); inspected 5 assigned files (25 inspected across reviewers): README.md, tools/cfl/README.md, tools/cfl/internal/cmd/OUTPUT_SPEC.md, tools/cfl/internal/cmd/page/export.go, tools/cfl/internal/cmd/page/page.go; skipped: none; constraints: No sibling ../cli-common or ../.github checkouts were present, so family-wide Open CLI Collective standards (command-surface.md, output-and-rendering.md, repo-layout.md) could not be checked directly; review relied on repo-local docs and existing command precedent (attachment/download.go) instead.
  • security:code-auditor — complete (constrained); inspected 17 assigned files (25 inspected across reviewers): skills/Confluence/CliReference.md, skills/Confluence/Workflows/ManagePage.md, tools/cfl/CHANGELOG.md, tools/cfl/api/export.go, tools/cfl/api/testdata/pdf_export_progress_running.json, tools/cfl/api/testdata/pdf_export_progress_succeeded.json, tools/cfl/api/testdata/pdf_export_start.html, tools/cfl/api/testdata/pdf_export_start_forbidden.html, tools/cfl/api/testdata/pdf_export_start_not_found.html, tools/cfl/integration-tests.md, tools/cfl/internal/cmd/page/export.go, tools/cfl/internal/cmd/page/testdata/export_document.pdf, tools/cfl/internal/cmd/page/testdata/export_page.json, tools/cfl/internal/cmd/page/testdata/export_progress_failed.json, tools/cfl/internal/cmd/page/testdata/export_progress_running.json, tools/cfl/internal/cmd/page/testdata/export_progress_succeeded.json, tools/cfl/internal/cmd/page/testdata/export_start.html; skipped: none; constraints: none
  • architecture:solid-reviewer-agnostic — complete (constrained); inspected 4 assigned files (25 inspected across reviewers): tools/cfl/api/export.go, tools/cfl/internal/cmd/page/export.go, tools/cfl/internal/cmd/page/page.go, tools/cfl/internal/present/mutation.go; skipped: none; constraints: Ran go test -count=1 for api, internal/cmd/page and internal/present with CGO_ENABLED=0 (all pass). -race could not run because the sandbox's clang module-cache path contains a space; make check and golangci-lint were not run. The earlier findings (typed start errors and the injectable poll interval) are settled threads and were not raised again. Only 58f3d12 (createExportTemp, umask-derived mode) is new since the last pass; it changes only export.go and its test.
Inspected files (25)
  • README.md
  • skills/Confluence/CliReference.md
  • skills/Confluence/Workflows/ManagePage.md
  • tools/cfl/CHANGELOG.md
  • tools/cfl/README.md
  • tools/cfl/api/export.go
  • tools/cfl/api/export_test.go
  • tools/cfl/api/testdata/pdf_export_progress_running.json
  • tools/cfl/api/testdata/pdf_export_progress_succeeded.json
  • tools/cfl/api/testdata/pdf_export_start.html
  • tools/cfl/api/testdata/pdf_export_start_forbidden.html
  • tools/cfl/api/testdata/pdf_export_start_not_found.html
  • tools/cfl/integration-tests.md
  • tools/cfl/internal/cmd/OUTPUT_SPEC.md
  • tools/cfl/internal/cmd/page/export.go
  • tools/cfl/internal/cmd/page/export_test.go
  • tools/cfl/internal/cmd/page/page.go
  • tools/cfl/internal/cmd/page/testdata/export_document.pdf
  • tools/cfl/internal/cmd/page/testdata/export_page.json
  • tools/cfl/internal/cmd/page/testdata/export_progress_failed.json
  • tools/cfl/internal/cmd/page/testdata/export_progress_running.json
  • tools/cfl/internal/cmd/page/testdata/export_progress_succeeded.json
  • tools/cfl/internal/cmd/page/testdata/export_start.html
  • tools/cfl/internal/present/mutation.go
  • tools/cfl/internal/present/mutation_test.go

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


Completed in 1m 08s | ~$1.13 (est.) | claude-sonnet-5, claude-opus-5-5 | cr 0.10.318
Field Value
Model claude-sonnet-5, claude-opus-5-5
Reviewers go:implementation-tests, policies:conventions, security:code-auditor, architecture:solid-reviewer-agnostic
Engine claude_cli · claude-sonnet-5, claude-opus-5-5
Reviewed by cr · monit-reviewer
Duration 1m 08s wall · 2m 27s compute
Cost ~$1.13 (est.)
Pricing basis anthropic-public-2026-09-23
Tokens 44 in / 6.5k out

Per-workstream usage

  • go:implementation-tests — claude-sonnet-5
    • In: 12
    • Out: 1.5k
    • Cache read: 705.1k
    • Cache create: 44.2k
    • Cost: ~$0.33 (est.)
    • Duration: 40s
  • policies:conventions — claude-sonnet-5
    • In: 10
    • Out: 1.4k
    • Cache read: 520.4k
    • Cache create: 37.8k
    • Cost: ~$0.27 (est.)
    • Duration: 24s
  • security:code-auditor — claude-sonnet-5
    • In: 8
    • Out: 1.7k
    • Cache read: 408.4k
    • Cache create: 44.8k
    • Cost: ~$0.28 (est.)
    • Duration: 36s
  • architecture:solid-reviewer-agnostic — claude-opus-5-5
    • In: 8
    • Out: 1.5k
    • Cache read: 347.7k
    • Cache create: 7.4k
    • Cost: ~$0.16 (est.)
    • Duration: 31s
  • orchestrator-rollup — claude-sonnet-5
    • In: 6
    • Out: 373
    • Cache read: 218.6k
    • Cache create: 11.2k
    • Cost: ~$0.09 (est.)
    • Duration: 14s

@piekstra
piekstra marked this pull request as ready for review October 1, 2026 12:27

@monit-reviewer monit-reviewer 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: 50428da16bc6
Profile: claude-monit-reviewer - Posting as: monit-reviewer

Summary

Reviewer Findings
go:implementation-tests 0
policies:conventions 0
security:code-auditor 0
architecture:solid-reviewer-agnostic 0

Reviewer Coverage

  • go:implementation-tests — complete (constrained); inspected 7 assigned files (25 inspected across reviewers): tools/cfl/api/export.go, tools/cfl/api/export_test.go, tools/cfl/internal/cmd/page/export.go, tools/cfl/internal/cmd/page/export_test.go, tools/cfl/internal/cmd/page/page.go, tools/cfl/internal/present/mutation.go, tools/cfl/internal/present/mutation_test.go; skipped: none; constraints: This revision is a merge of main into the PR branch; the assigned files are unchanged from the prior reviewed revision, which already had all findings resolved.
  • policies:conventions — complete (constrained); inspected 5 assigned files (25 inspected across reviewers): README.md, tools/cfl/README.md, tools/cfl/internal/cmd/OUTPUT_SPEC.md, tools/cfl/internal/cmd/page/export.go, tools/cfl/internal/cmd/page/page.go; skipped: none; constraints: No sibling ../cli-common or ../.github checkouts were present, so family-wide Open CLI Collective standards (command-surface.md, output-and-rendering.md, repo-layout.md) could not be checked directly; review relied on repo-local docs and existing command precedent (attachment/download.go) instead.
  • security:code-auditor — complete (constrained); inspected 17 assigned files (25 inspected across reviewers): skills/Confluence/CliReference.md, skills/Confluence/Workflows/ManagePage.md, tools/cfl/CHANGELOG.md, tools/cfl/api/export.go, tools/cfl/api/testdata/pdf_export_progress_running.json, tools/cfl/api/testdata/pdf_export_progress_succeeded.json, tools/cfl/api/testdata/pdf_export_start.html, tools/cfl/api/testdata/pdf_export_start_forbidden.html, tools/cfl/api/testdata/pdf_export_start_not_found.html, tools/cfl/integration-tests.md, tools/cfl/internal/cmd/page/export.go, tools/cfl/internal/cmd/page/testdata/export_document.pdf, tools/cfl/internal/cmd/page/testdata/export_page.json, tools/cfl/internal/cmd/page/testdata/export_progress_failed.json, tools/cfl/internal/cmd/page/testdata/export_progress_running.json, tools/cfl/internal/cmd/page/testdata/export_progress_succeeded.json, tools/cfl/internal/cmd/page/testdata/export_start.html; skipped: none; constraints: none
  • architecture:solid-reviewer-agnostic — complete (constrained); inspected 4 assigned files (25 inspected across reviewers): tools/cfl/api/export.go, tools/cfl/internal/cmd/page/export.go, tools/cfl/internal/cmd/page/page.go, tools/cfl/internal/present/mutation.go; skipped: none; constraints: Ran go test -count=1 for api, internal/cmd/page and internal/present with CGO_ENABLED=0 on the merged head (all pass). -race could not run because the sandbox's clang module-cache path contains a space; make check and golangci-lint were not run. Since the last pass (58f3d12), the only commits are merges from main; none of the assigned files changed. The earlier findings are settled threads and were not raised again.
Inspected files (25)
  • README.md
  • skills/Confluence/CliReference.md
  • skills/Confluence/Workflows/ManagePage.md
  • tools/cfl/CHANGELOG.md
  • tools/cfl/README.md
  • tools/cfl/api/export.go
  • tools/cfl/api/export_test.go
  • tools/cfl/api/testdata/pdf_export_progress_running.json
  • tools/cfl/api/testdata/pdf_export_progress_succeeded.json
  • tools/cfl/api/testdata/pdf_export_start.html
  • tools/cfl/api/testdata/pdf_export_start_forbidden.html
  • tools/cfl/api/testdata/pdf_export_start_not_found.html
  • tools/cfl/integration-tests.md
  • tools/cfl/internal/cmd/OUTPUT_SPEC.md
  • tools/cfl/internal/cmd/page/export.go
  • tools/cfl/internal/cmd/page/export_test.go
  • tools/cfl/internal/cmd/page/page.go
  • tools/cfl/internal/cmd/page/testdata/export_document.pdf
  • tools/cfl/internal/cmd/page/testdata/export_page.json
  • tools/cfl/internal/cmd/page/testdata/export_progress_failed.json
  • tools/cfl/internal/cmd/page/testdata/export_progress_running.json
  • tools/cfl/internal/cmd/page/testdata/export_progress_succeeded.json
  • tools/cfl/internal/cmd/page/testdata/export_start.html
  • tools/cfl/internal/present/mutation.go
  • tools/cfl/internal/present/mutation_test.go

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


Completed in 2m 49s | ~$3.96 (est.) | claude-sonnet-5, claude-opus-5-5 | cr 0.10.318
Field Value
Model claude-sonnet-5, claude-opus-5-5
Reviewers go:implementation-tests, policies:conventions, security:code-auditor, architecture:solid-reviewer-agnostic
Engine claude_cli · claude-sonnet-5, claude-opus-5-5
Reviewed by cr · monit-reviewer
Duration 2m 49s wall · 6m 58s compute
Cost ~$3.96 (est.)
Pricing basis anthropic-public-2026-09-23
Tokens 40 in / 6.6k out

Per-workstream usage

  • go:implementation-tests — claude-sonnet-5
    • In: 10
    • Out: 1.6k
    • Cache read: 495.8k
    • Cache create: 213.6k
    • Cost: ~$0.97 (est.)
    • Duration: 1m 39s
  • policies:conventions — claude-sonnet-5
    • In: 10
    • Out: 1.8k
    • Cache read: 457.2k
    • Cache create: 188.3k
    • Cost: ~$0.86 (est.)
    • Duration: 1m 40s
  • security:code-auditor — claude-sonnet-5
    • In: 8
    • Out: 1.4k
    • Cache read: 331.1k
    • Cache create: 192.5k
    • Cost: ~$0.85 (est.)
    • Duration: 1m 30s
  • architecture:solid-reviewer-agnostic — claude-opus-5-5
    • In: 8
    • Out: 1.6k
    • Cache read: 289.3k
    • Cache create: 93.2k
    • Cost: ~$0.83 (est.)
    • Duration: 1m 40s
  • orchestrator-rollup — claude-sonnet-5
    • In: 4
    • Out: 343
    • Cache read: 53.9k
    • Cache create: 108.0k
    • Cost: ~$0.45 (est.)
    • Duration: 26s

@piekstra
piekstra merged commit a3cd182 into main Oct 1, 2026
16 checks passed
@piekstra
piekstra deleted the piekstra/cfl-page-export-pdf branch October 1, 2026 12:38
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.

2 participants