Summary
Follow-up to #58. While the main README.md (now fixed in #61) was the most visible broken-link surface, the same canonical-name mismatch and relative-path errors exist inside the doc tree itself. A reader on docs/core-concepts/runtime-engine.md who clicks "Memory" or "Tools" gets a GitHub 404, and the docs/security/overview.md "Security Hardening Checklist" links to a half-dozen siblings that all 404.
Concretely: 34 broken in-doc links across 14 of the ~35 doc files, mapping to 16 unique missing targets. The CI workflow added in #61 only checks README.md; this issue is about expanding the same gate to the rest of the doc tree, plus a one-pass cleanup of the existing breakage.
Audit (run from repo root on main)
python3 - <<'PY'
import re, os
broken = []
for root, _, files in os.walk("docs"):
for f in files:
if not f.endswith(".md"): continue
path = os.path.join(root, f)
for m in re.finditer(r"\[([^\]]+)\]\(([^)]+\.md)(#[^)]*)?\)", open(path).read()):
target = m.group(2)
if target.startswith("http"): continue
resolved = os.path.normpath(os.path.join(os.path.dirname(path), target))
if not os.path.exists(resolved):
broken.append((path, target))
print(f"{len(broken)} broken")
PY
Result on main after #61 merges: 34 broken.
Categorized breakdown
The 34 occurrences fall into three causes:
Cause 1: Missing ../ — link assumes "relative to docs/ root" but Markdown resolves relative to the file's directory
Most of the broken security/guardrails.md / security/egress.md / tools.md / memory.md / runtime.md / dashboard.md references are this. The author wrote the path as if docs/ were the project root.
| File |
Broken link |
Should be |
docs/core-concepts/channels.md |
runtime.md |
runtime-engine.md (same directory) |
docs/core-concepts/hooks.md |
security/guardrails.md (×2) |
../security/guardrails.md |
docs/core-concepts/how-forge-works.md |
dashboard.md |
../reference/web-dashboard.md |
docs/core-concepts/how-forge-works.md |
security/egress.md |
../security/egress-control.md |
docs/core-concepts/runtime-engine.md |
tools.md |
tools-and-builtins.md |
docs/core-concepts/runtime-engine.md |
memory.md |
memory-system.md |
docs/core-concepts/runtime-engine.md |
security/guardrails.md |
../security/guardrails.md |
docs/core-concepts/skill-md-format.md |
security/egress.md |
../security/egress-control.md |
docs/core-concepts/tools-and-builtins.md |
security/guardrails.md (×3) |
../security/guardrails.md |
docs/core-concepts/tools-and-builtins.md |
runtime.md |
runtime-engine.md |
docs/core-concepts/tools-and-builtins.md |
memory.md |
memory-system.md |
docs/skills/embedded-skills.md |
security/guardrails.md |
../security/guardrails.md |
docs/skills/embedded-skills.md |
tools.md |
../core-concepts/tools-and-builtins.md |
docs/skills/skills-cli.md |
dashboard.md |
../reference/web-dashboard.md |
docs/skills/writing-custom-skills.md |
security/egress.md |
../security/egress-control.md |
docs/skills/writing-custom-skills.md |
security/guardrails.md |
../security/guardrails.md |
docs/reference/cli-reference.md |
dashboard.md |
web-dashboard.md (same directory) |
docs/reference/web-dashboard.md |
skills.md |
../skills/writing-custom-skills.md or ../core-concepts/skill-md-format.md (choose) |
docs/deployment/production-checklist.md |
command-integration.md |
../reference/command-integration.md |
docs/getting-started/contributing.md |
command-integration.md |
../reference/command-integration.md |
docs/getting-started/contributing.md |
../README.md |
../../README.md (two levels up to repo root) |
Cause 2: Canonical-name mismatch — same pattern as #58 inside docs/security/overview.md
This single file accounts for 10 of the 34 broken links. It was written before the docs were renamed to the *-control, *-management, *-signing pattern and never updated.
| Broken link |
Should be |
egress.md (×2) |
egress-control.md |
secrets.md (×2) |
secret-management.md |
signing.md (×2) |
build-signing.md |
../architecture.md |
../core-concepts/how-forge-works.md |
../tools.md |
../core-concepts/tools-and-builtins.md |
../skills.md |
../skills/writing-custom-skills.md |
../commands.md |
../reference/cli-reference.md |
The first six are sibling-renames within docs/security/; the last four are the same canonical-name mismatch issue that #58 cleaned up in the README.
Per-file blast radius
| File |
Broken links |
docs/security/overview.md |
10 |
docs/core-concepts/tools-and-builtins.md |
5 |
docs/core-concepts/runtime-engine.md |
3 |
docs/core-concepts/hooks.md |
2 |
docs/core-concepts/how-forge-works.md |
2 |
docs/getting-started/contributing.md |
2 |
docs/skills/embedded-skills.md |
2 |
docs/skills/writing-custom-skills.md |
2 |
| 6 other files |
1 each |
Proposed fix shape
One PR, two halves.
Half 1: Apply the remap
Walk the table above. 33 of 34 are mechanical string substitutions per the remap. The one judgment call is docs/reference/web-dashboard.md → "[SKILL.md format]" — should it point at core-concepts/skill-md-format.md (the actual format reference) or skills/writing-custom-skills.md (the broader skills doc the README links to)? Issue: pick one — recommendation is core-concepts/skill-md-format.md since the link's anchor text is literally "SKILL.md format".
Half 2: Extend the CI link check to the whole doc tree
.github/workflows/docs-links.yaml (added in #61) currently checks only README.md. Extend the script to walk docs/**/*.md too, with the same fail-on-broken behavior. Path filter is already correct (docs/**).
- name: Verify all relative .md links resolve
run: |
python3 - <<'PY'
import re, os, sys
bad = []
targets = ["README.md"] + [os.path.join(r, f) for r, _, fs in os.walk("docs") for f in fs if f.endswith(".md")]
for path in targets:
for m in re.finditer(r"\[([^\]]+)\]\(([^)]+\.md)(#[^)]*)?\)", open(path).read()):
t = m.group(2)
if t.startswith("http"): continue
r = os.path.normpath(os.path.join(os.path.dirname(path), t))
if not os.path.exists(r):
bad.append(f"{path} -> {t}")
if bad:
print("Broken doc links:")
for line in bad: print(f" {line}")
sys.exit(1)
print(f"All {len(targets)} doc files: relative .md links resolve")
PY
Acceptance criteria
Out of scope
- Anchor-fragment validation (e.g.
#some-heading). Could be a separate enhancement to the same workflow if it ever becomes worth checking; trivially harder because it requires reading each linked file's headings.
- External (
https://...) link rot — different problem, different tooling.
Related
Surfaced during the sync-docs runs for v0.10.0 and the #58 audit.
Summary
Follow-up to #58. While the main
README.md(now fixed in #61) was the most visible broken-link surface, the same canonical-name mismatch and relative-path errors exist inside the doc tree itself. A reader ondocs/core-concepts/runtime-engine.mdwho clicks "Memory" or "Tools" gets a GitHub 404, and the docs/security/overview.md "Security Hardening Checklist" links to a half-dozen siblings that all 404.Concretely: 34 broken in-doc links across 14 of the ~35 doc files, mapping to 16 unique missing targets. The CI workflow added in #61 only checks
README.md; this issue is about expanding the same gate to the rest of the doc tree, plus a one-pass cleanup of the existing breakage.Audit (run from repo root on
main)Result on
mainafter #61 merges:34 broken.Categorized breakdown
The 34 occurrences fall into three causes:
Cause 1: Missing
../— link assumes "relative to docs/ root" but Markdown resolves relative to the file's directoryMost of the broken
security/guardrails.md/security/egress.md/tools.md/memory.md/runtime.md/dashboard.mdreferences are this. The author wrote the path as ifdocs/were the project root.docs/core-concepts/channels.mdruntime.mdruntime-engine.md(same directory)docs/core-concepts/hooks.mdsecurity/guardrails.md(×2)../security/guardrails.mddocs/core-concepts/how-forge-works.mddashboard.md../reference/web-dashboard.mddocs/core-concepts/how-forge-works.mdsecurity/egress.md../security/egress-control.mddocs/core-concepts/runtime-engine.mdtools.mdtools-and-builtins.mddocs/core-concepts/runtime-engine.mdmemory.mdmemory-system.mddocs/core-concepts/runtime-engine.mdsecurity/guardrails.md../security/guardrails.mddocs/core-concepts/skill-md-format.mdsecurity/egress.md../security/egress-control.mddocs/core-concepts/tools-and-builtins.mdsecurity/guardrails.md(×3)../security/guardrails.mddocs/core-concepts/tools-and-builtins.mdruntime.mdruntime-engine.mddocs/core-concepts/tools-and-builtins.mdmemory.mdmemory-system.mddocs/skills/embedded-skills.mdsecurity/guardrails.md../security/guardrails.mddocs/skills/embedded-skills.mdtools.md../core-concepts/tools-and-builtins.mddocs/skills/skills-cli.mddashboard.md../reference/web-dashboard.mddocs/skills/writing-custom-skills.mdsecurity/egress.md../security/egress-control.mddocs/skills/writing-custom-skills.mdsecurity/guardrails.md../security/guardrails.mddocs/reference/cli-reference.mddashboard.mdweb-dashboard.md(same directory)docs/reference/web-dashboard.mdskills.md../skills/writing-custom-skills.mdor../core-concepts/skill-md-format.md(choose)docs/deployment/production-checklist.mdcommand-integration.md../reference/command-integration.mddocs/getting-started/contributing.mdcommand-integration.md../reference/command-integration.mddocs/getting-started/contributing.md../README.md../../README.md(two levels up to repo root)Cause 2: Canonical-name mismatch — same pattern as #58 inside
docs/security/overview.mdThis single file accounts for 10 of the 34 broken links. It was written before the docs were renamed to the
*-control,*-management,*-signingpattern and never updated.egress.md(×2)egress-control.mdsecrets.md(×2)secret-management.mdsigning.md(×2)build-signing.md../architecture.md../core-concepts/how-forge-works.md../tools.md../core-concepts/tools-and-builtins.md../skills.md../skills/writing-custom-skills.md../commands.md../reference/cli-reference.mdThe first six are sibling-renames within
docs/security/; the last four are the same canonical-name mismatch issue that #58 cleaned up in the README.Per-file blast radius
docs/security/overview.mddocs/core-concepts/tools-and-builtins.mddocs/core-concepts/runtime-engine.mddocs/core-concepts/hooks.mddocs/core-concepts/how-forge-works.mddocs/getting-started/contributing.mddocs/skills/embedded-skills.mddocs/skills/writing-custom-skills.mdProposed fix shape
One PR, two halves.
Half 1: Apply the remap
Walk the table above. 33 of 34 are mechanical string substitutions per the remap. The one judgment call is
docs/reference/web-dashboard.md → "[SKILL.md format]"— should it point atcore-concepts/skill-md-format.md(the actual format reference) orskills/writing-custom-skills.md(the broader skills doc the README links to)? Issue: pick one — recommendation iscore-concepts/skill-md-format.mdsince the link's anchor text is literally "SKILL.md format".Half 2: Extend the CI link check to the whole doc tree
.github/workflows/docs-links.yaml(added in #61) currently checks onlyREADME.md. Extend the script to walkdocs/**/*.mdtoo, with the same fail-on-broken behavior. Path filter is already correct (docs/**).Acceptance criteria
.mdlink insidedocs/**resolves to a file that exists onmain— same standard docs: fix 21 broken doc links in README (closes #58) #61 applied to the README.docs/**/*.md, so this can't regress on future PRs.Out of scope
#some-heading). Could be a separate enhancement to the same workflow if it ever becomes worth checking; trivially harder because it requires reading each linked file's headings.https://...) link rot — different problem, different tooling.Related
.github/workflows/docs-links.yaml; this issue extends it.Surfaced during the sync-docs runs for v0.10.0 and the #58 audit.