feat(check): warn on internal links that do not resolve #301
No reviewers
Labels
No labels
bug
enhancement
needs-triage
ready-for-agent
ready-for-human
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Cloonar/deckle!301
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "afk/195"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #195
What
One owner for "does this stored link resolve": new
internal/linkresolve. It covers page permalinks,#c-<uid>element anchors (translation file first, then the source) and bundle sub-paths, and returns a verdict: resolved, no page, no element or no file. It is called by:harness checkGET /api/link/resolve(handleLinkResolve)disabledLinkTargets)No second copy of the rule is left in
internal/serverorinternal/checkcmd.ElementAnchorPrefixmoved there too.harness checkwarns in areacontent, on the linking file, once per distinct destination. It checks every non-empty, root-relative,Page-kind destination thatlinkrefs.Destinationsyields from pages and translation files, drafts included. The three messages:links to <dest>, but no page has that addresslinks to <dest>, but <permalink> has no element with uid "<uid>"links to <dest>, but the bundle of <permalink> has no file "<sub>"GET /api/link/resolvenow answersfound: true(kindpage, label the file name, no address or fallback) for a bundle file that exists.Publish no longer matches a content-path spelling such as
posts/b/index.md. A permalink, element or bundle-file link to a draft page still warns.linkrefs.Destinationsnow also skips the top-level front-matterurlkey, which is the page's own address. Without this, every translation with aurl(/kontakt/intestdata/multilingual-site) and everyurlwithout a trailing slash got a false warning. The rename rewrite ofurlis unchanged.Docs:
docs/harness.md"Check",docs/api.md(resolve, publish, page refs),scaffold/site/AGENTS.md"Verifying a change", theverify-siteandmigrate-siteskills, and bothlinkrefscomments.Decisions within the brief
_index.md) files count only directly in its directory; a leaf bundle's (index.md) count at any depth. This is how Hugo publishes them..md, is reported as "no page", so/goneand/posts/b/index.mddon't claim a missing bundle file.Verification
go test ./...andgo vet ./...pass (withweb/distbuilt), andgofmtis clean.harness initsite: exactly the three link warnings, exit 0.internal/linkresolve: unit tests for the rule.internal/checkcmd:TestDanglingLinks(the issue's reproduction, then fixed),TestLinksWithoutFalsePositivesandTestTranslationLinks.internal/server: bundle file found and missing in resolve, andTestPublishAllCheckIgnoresContentPaths.Worth knowing (not changed here)
testdata/library-sitenow warns onpage-teasers section="/posts". The page-teasers sidecar says the theme still resolves that legacy spelling throughGetPage, but resolve already reported itfound: false. The owner follows the issue: an exact permalink only.testdata/sitewarns onimage="/images/hero.png". The file really does not exist.static/would draw a "no file" warning. No scaffold shipsstatic/, but a migrated site might, so this may be worth a follow-up.🤖 Generated with Claude Code
[autoland] verdict: pass
Verdict: CONCERNS (non-blocking): approved, not auto-merged.
What I relied on:
labctl pr checks 301came backsuccess.ci / build-and-testran the frontend build and tests,go vet ./...,go test ./...(the drift tests included) and theharnessbuild. I also rangofmt -lon the changed Go files, which was clean. The head already containsorigin/main, so there is nothing to merge in. The title is in Conventional Commits form and the body hasCloses #195. The diff matches the brief:internal/linkresolve, and resolve, publish andharness checkall call it. No copy is left ininternal/serverorinternal/checkcmd.docs/harness.md,docs/api.md,AGENTS.md, theverify-siteandmigrate-siteskills, and bothlinkrefscomments are updated.urlkey inlinkrefs.Destinations(internal/linkrefs/linkrefs.go:387) is needed to avoid false positives, and the matching/api/page/refschange is documented indocs/api.md.Concern:
harness checkwarns on a page value the engine accepts and the theme rendersinternal/element/ftype_page.go:22-30(TypePage) deliberately accepts apageparam missing only its trailing slash (/postsfor/posts/). The reason given there: params upgraded from text, namely the scaffolded page-teaserssection, "left exactly that hand-typed shape in existing content, and Hugo'ssite.GetPageresolves it at render time".scaffold/editor/elements/page-teasers.yaml:3-5promises the same ("legacy hand-typed paths like "/posts" still resolve").linkresolve.Resolver.Resolve(internal/linkresolve/linkresolve.go:147) matches the exact permalink only. Socheckcmd.links(internal/checkcmd/check.go:600) reports such a value aslinks to /posts, but no page has that address.checkcmd.Runontestdata/library-sitegiveswarning content/_index.md: links to /posts, but no page has that addressforpage-teasers section="/posts"(testdata/library-site/content/_index.md:46). The save path accepts that value and the theme renders it./api/link/resolvealready answeredfound: falsefor this value. Still, the engine now gives two different answers to "does this value name an existing page":element.pageKind.siteallows the slash,linkresolvedoes not. The brief also says the check produces no false positives./posts/, is harmless. The maintainer should decide whether the owner adopts the trailing-slash allowance forpage-typed values, or the warning stands and the legacy shape is migrated.Minor, related (already disclosed in the PR body): a link to a file under
static/(e.g./images/x.png) gets the misleadingthe bundle of / has no file "images/x.png"(internal/linkresolve/linkresolve.go:184). No scaffold shipsstatic/, but a migrated site could, so this is worth a follow-up issue.Pushed a third commit addressing the review's concern directly rather than as a follow-up: the page field type's save check no longer carries a trailing-slash tolerance of its own. `linkresolve.Resolver.Page` (exact permalink of a real page) is exported, `pageops`' `element.Site` calls it, and `TypePage` drops the retry — so save, `harness check`, resolve, publish and the rename rewrite all give one answer for `/posts`.
Consequences, in the PR body's terms:
"Does this stored value name a page" was answered twice: the page field type's save check tolerated a value missing only its trailing slash ("/posts" for "/posts/", the hand-typed shape of params upgraded from text), while the link check, GET /api/link/resolve, publish and the rename rewrite all held the exact permalink. So `harness check` warned on a value the same engine saved, and a rename of the section silently left the value behind. One owner now: linkresolve.Resolver.Page (the exact permalink of a real page) is exported, pageops' element.Site calls it, and TypePage drops its retry. A legacy slashless value is a dangling reference everywhere: `harness check` reports it and the element's next save is refused until the page is picked again. AGENTS.md, the page-teasers and news-list sidecars, the page-teasers template comment and the add-element reference stop promising the tolerance; the library fixture stores the canonical form, so the theme test pins the render contract alone. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>Merged
main(081a4c3, #306 — machine-owned, versioned sidecars andharness migrate) intoafk/195as33c9f4f; four conflicts, all resolved without a semantic choice:scaffold/editor/elements/page-teasers.yaml,scaffold/features/news/editor/elements/news-list.yaml: tookmain's canonical form. This branch only edited the prose comments (dropping the "/postsstill resolves" promise);mainreplaced those comments with the machine-owned header, so the promise is gone either way and the shipped-sidecar canonical test holds.scaffold/site/AGENTS.md"Verifying a change" and theverify-siteskill: union of both lists (outdated / non-canonical sidecars frommain, dangling links from this branch).docs/harness.md: git auto-merged the Links. paragraph belowmain's new## Migratesection; moved it back to the end of## Check, where the warnings list's "see the next paragraph" points.Re-verified on the merged tree:
gofmtclean,go vet ./...,go test ./...(all packages, drift tests included, Hugo 0.167.0 on PATH,web/distbuilt) andgo build ./cmd/harnesspass. Smoke: freshharness init→ no problems; the issue's reproduction → exactly the three link warnings, exit 0.[autoland] verdict: pass
Verdict: CONCERNS (non-blocking) — validated, awaiting the maintainer's merge confirmation.
Signal relied on:
ci / build-and-testgreen on33c9f4f(run 312: frontend build + tests,go vet ./...,go test ./...incl. the drift tests,harnessbuild), plus the same Go suite run locally on the resolved tree (the merge-from-maincommit, see the comment above). Title is Conventional Commits; body carriesCloses #195.Checked against #195's brief: one owner,
internal/linkresolve, called byharness check,GET /api/link/resolveand publish's draft-link check; no copy left ininternal/serverorinternal/checkcmd. Publish's content-path match is gone. Resolve answersfound: truefor an existing bundle file. Every acceptance criterion has a test, and the issue's reproduction gives exactly three warnings, exit 0. Docs and skills are updated:docs/harness.mdCheck,docs/api.md, AGENTS.md,verify-site,migrate-site, bothlinkrefscomments.Beyond the brief, maintainer-owned: commit
e101d17removes thepagefield type's trailing-slash tolerance (internal/element/ftype_page.go), so a legacy/postsvalue is now refused on save until the page is picked again, andharness checklists it. This resolves the earlier review's "two answers" concern and is documented in AGENTS.md, the sidecar and template comments and the add-element reference. Noted as a deliberate behaviour change for existing sites. It is not a concern.Concern: false positive for
static/files. A link to a real file understatic/warns. Reproduced:static/files/brochure.pdflinked as/files/brochure.pdfgiveslinks to /files/brochure.pdf, but the bundle of / has no file "files/brochure.pdf". This comes frominternal/linkresolve/linkresolve.goResolve, where the home page's branch bundle is the prefix of every target. The brief asks for no false positives. It is only a warning, no scaffold shipsstatic/, and it is disclosed in the PR body, but a migrated site may have such files. Worth a follow-up issue.