Skip to content

Keep CLAUDE.md off the pkgdown site (#42) - #43

Merged
NewGraphEnvironment merged 2 commits into
mainfrom
42-keep-claude-md-off-the-pkgdown-site
Aug 31, 2026
Merged

Keep CLAUDE.md off the pkgdown site (#42)#43
NewGraphEnvironment merged 2 commits into
mainfrom
42-keep-claude-md-off-the-pkgdown-site

Conversation

@NewGraphEnvironment

@NewGraphEnvironment NewGraphEnvironment commented Aug 31, 2026

Copy link
Copy Markdown
Owner

Closes #42.

CLAUDE.html has been live on fly's public pkgdown site. pkgdown renders every
root-level .md except a hardcoded allowlist, and there is no config option to exclude
one — .Rbuildignore governs R CMD build, not the docs site.

Three changes, and the first alone would not have worked

rm -f CLAUDE.md before build_site() stops all three copies being produced — the rendered page, a markdown twin served as-is, and the search index (227 hits for CLAUDE). Deleting only CLAUDE.html looks like a fix and achieves nothing
an allowlist gate after the build closes the mechanism, not one file. Without it the next root-level .md anyone adds leaks silently — which is how this went unnoticed here
clean: true on the deploy with clean: false the action never deletes, so removing the file from the build would have left the published copies exactly where they are

Checked before flipping clean

clean: true deletes anything on gh-pages not present in docs/, so:

  • no CNAME (404) — the custom domain comes from the org site repo, nothing to break
  • no dev/ — no versioned docs to lose
  • docs/ is untracked (0 files in git), so the whole site is CI-generated and nothing
    on the branch is a hand-added asset

Verified, not reasoned

pkgdown:::package_mds() — the function that actually makes this decision — returns
CLAUDE.md and nothing else for this repo, and returns none once the file is gone.

The gate was extracted verbatim from the YAML that ships rather than retyped, and run
against five cases:

case expected got
clean site 0 0
CLAUDE.html present 1 1
only CLAUDE.md present 1 1
both copies 1, named once 1, named once
empty docs/ 1 1
a page named authors index 1 1
a page named index.md.html 1 1
a glob metacharacter in a filename 1 1
a plausible future root page (NOTES) 1 1

Two of these were failures the first time. The gate went through two rounds, and
neither weakness was visible by reading:

  • the first draft looped over docs/*.html and exited 0 on an empty docs/ — an
    affirmative claim of success about a build that produced nothing
  • the second used case " $allowed " in *" $b "*), which reads like a token test and is
    a substring test, so authors index passed. Stripping both suffixes
    unconditionally had the same shape, reducing index.md.html to index

Both fail toward pass. Neither is reachable through pkgdown today — R CMD check
rejects a filename with a space — so this was a guard weaker than it read rather than a
live leak. Worth fixing anyway, since the entire value of the step is that someone trusts
it a year from now. A loop over nothing exits 0, so without an explicit
population check a build that produced no site at all would have reported "Root pages are
all declared" — a guard making an affirmative claim of success about work that never
happened. The gate now fails on a missing docs/index.html first.

The pull_request trigger runs the build and the gate (deploy is skipped), so this PR's
own CI exercises both.

Not a release

.github/ is in .Rbuildignore, so the built package is byte-identical to v0.7.0. No
NEWS entry, no version bump, no tag — there is nothing a user could observe.

After merge

The first deploy with clean: true removes the published copies. Worth confirming
https://www.newgraphenvironment.com/fly/CLAUDE.html returns 404 afterwards, and that
search.json no longer carries the text. De-indexing is a separate Search Console
request, and only after the URL 404s — a robots.txt block first would stop crawlers
seeing the 404 and keep stale entries alive longer than doing nothing.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GBKqedyBysV7hB4DuL98ZR

NewGraphEnvironment and others added 2 commits August 31, 2026 08:32
pkgdown renders every root-level .md except a hardcoded allowlist, with no
config option to exclude one, so `CLAUDE.html` has been live on the public site.
`.Rbuildignore` does not reach it — that governs `R CMD build`, not the docs.

Three changes, and the first alone would not have worked:

- `rm -f CLAUDE.md` before `build_site()`, so none of the three copies is
  produced. There are three, not one: the rendered page, a verbatim copy of the
  source served as-is, and the full-text search index (227 hits for CLAUDE).
- An allowlist gate after the build, because the point is to close the mechanism
  rather than delete one file. Without it the next root-level .md anyone adds
  leaks silently, which is how this went unnoticed.
- `clean: true` on the deploy. With `clean: false` the action never deletes, so
  every file ever deployed stays on gh-pages whether or not the source still
  produces it — removing CLAUDE.md from the build would have left the published
  copies exactly where they were. Checked before flipping: no CNAME on gh-pages,
  no dev/ versioned docs, and docs/ is untracked so nothing there is hand-added.

Verified rather than reasoned. `pkgdown:::package_mds()` returns `CLAUDE.md` and
nothing else for this repo, and returns none once the file is gone. The gate was
extracted verbatim from the YAML that ships and run against five cases: a clean
site (0), CLAUDE.html present (1), only CLAUDE.md present (1), both copies (1,
named once), and an empty docs/ (1). That last one is the trap — a loop over
nothing exits 0, so without an explicit check a build that produced no site at
all would have reported "all declared".

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01GBKqedyBysV7hB4DuL98ZR
`case " $allowed " in *" $b "*)` reads like a token test and is not — it matches
a contiguous substring of the list, so a root page named "authors index" passed.
Verified against the shipped script before fixing: exit 0 where it must be 1.
Stripping both suffixes unconditionally had the same shape, reducing
"index.md.html" to "index".

Neither is reachable through pkgdown today, and `R CMD check` rejects a filename
containing a space, so this is a guard that was weaker than it read rather than a
live leak. That is worth fixing anyway: the whole value of this step is that
someone trusts it a year from now.

Now an explicit token loop, with the suffix that actually matched stripped.
Re-tested across nine cases, extracted verbatim from the YAML rather than
retyped: clean site (0), CLAUDE.html (1), CLAUDE.md (1), both (1), empty docs/
(1), the substring bypass (1), a double suffix (1), a glob metacharacter in a
filename (1), and a plausible future root page (1).

Correction to the previous commit message, which cannot be edited: the second
published copy is a pandoc-regenerated markdown twin, not a verbatim copy of the
source. The content is published either way, so the three-copy count and the
remedy are unchanged. The issue and PR bodies have been corrected.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01GBKqedyBysV7hB4DuL98ZR
@NewGraphEnvironment
NewGraphEnvironment merged commit b4212f1 into main Aug 31, 2026
1 check passed
@NewGraphEnvironment
NewGraphEnvironment deleted the 42-keep-claude-md-off-the-pkgdown-site branch August 31, 2026 17:12
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.

CLAUDE.md is published on the pkgdown site, and clean: false means removing it would not unpublish it

1 participant