| title | ContentForest — Runbook |
|---|---|
| status | live |
| owner | Will Lamerton |
Operational reference for the people who actually keep this thing running. If you're here because something broke, jump to When something breaks at the bottom; it covers the common symptoms.
The daily cron at 08:00 UTC handles the routine case. For backfills, re-runs, or testing a specific release in CI, dispatch the workflow yourself:
- Open the daily-content workflow.
- Click Run workflow.
- Inputs:
product— slug fromconfig/products.json(e.g.nanocoder,nanotune,get-md,json-up).version— the release tag without thevprefix (e.g.1.25.2).dry_run—truewrites tocontent/_test/<product>/<version>/and labels the PRtest.falsewrites tocontent/<product>/<version>/and opens a real release PR.
- Submit. The run takes 8–15 minutes typical, longer worst case (
docs/handoff.md§1 has the cost ceiling).
The workflow opens one PR per pack with the auto-release label (and failed-validation if the hard rules failed). Review the diff, merge if it reads cleanly. The merge triggers a Cloudflare Pages rebuild and the pack appears at /p/<product>/<version>/ on the live site within a minute.
Idempotency guard: the workflow skips packs whose auto/release-<product>-<version> branch already exists on origin. To re-run an already-attempted pack: delete the orphan branch first (gh api -X DELETE repos/Nano-Collective/contentforest/git/refs/heads/auto/release-<product>-<version>).
The PR opens with the failed-validation label when the validator's hard rules fail after the orchestrator's per-agent retry budget is exhausted. The pack is still pushed so a human can read what the agent produced and finish it.
- Open the PR. The body summarises which agent failed (
failed_agentfield inmeta.json). - Open the
validate-contentworkflow run on the PR (Checks tab →validate-content). - Download the
validation-reportartifact — it's the structuredvalidation-report.jsonlisting every failure with{file, rule, expected, actual}. - Apply the fixes by hand in the PR's branch.
- Push. The validator runs again automatically (Layer 2 PR check). When it goes green, merge.
| Rule | What it means | Fix |
|---|---|---|
meta-exists / meta-parses |
meta.json missing or invalid JSON |
Re-write meta.json to the shape in prompts/agents/release-channels.md. |
file-exists |
A required channel or article file is missing | Write the file. Check the agent's prompt for the expected paths. |
frontmatter-shape / frontmatter-product / frontmatter-version |
Frontmatter missing fields, or product / version don't match the pack |
Fix the frontmatter to match the pack's product + version. |
channel-known |
frontmatter.channel not in config/channels.json |
Use one of linkedin, x, github-discussion, reddit. |
max-chars |
X post over 280 chars | Trim. |
max-words |
LinkedIn / Reddit / GH Discussion over the channel max | Restructure to be more concise; don't truncate mid-thought. |
forbidden-term |
Brand-doc forbidden phrase appeared (case-insensitive) | Re-read _refs/collective/organisation/brand.md, re-ground the wording. |
no-placeholder |
Unresolved {{TODO}} / {{RELEASE_URL}} / etc. |
Replace the placeholder with real content. |
link-product-repo |
Body doesn't contain the product repo root URL | Add the repo URL at least once. |
link-not-release |
Body contains a /releases/tag/ or /releases/download/ URL |
Replace with the repo root URL. |
articles-cap |
More than 3 articles | Delete the weakest article directory. |
article-slug-kebab-case / article-slug-matches / article-meta-shape |
Article structure issue | Rename the directory (kebab-case) or fix the article's meta.json. |
min-words shortfalls are hard failures — pad the body until it clears the floor, or lower min_words on the channel if the floor is wrong.
The workflow deletes its own branches if PR creation fails or if the run produces no channel files (system failure). But if a branch persists after a failed run:
gh api -X DELETE repos/Nano-Collective/contentforest/git/refs/heads/auto/release-<product>-<version>If a PR was closed without merging and you want to re-run the same <product>/<version>, delete the branch (above), then re-dispatch the workflow.
These directories are gitignored. To wipe them:
rm -rf content/_local content/_testscripts/fetch-refs.ts pulls from https://docs.nanocollective.org/llms.txt and every doc it lists. If the docs site is down or llms.txt 404s, the script fails loudly.
- Verify the docs site:
curl -I https://docs.nanocollective.org/llms.txt. If it 404s, the docs site is the problem — file an issue againstNano-Collective/docsand pause generation runs until it's up. - Partial outage (one or two docs 404 but
llms.txtis up): re-run withpnpm fetch-refs --allow-partial. The agent reads whatever made it into_refs/; missing brand or product docs may degrade output quality but won't crash the pipeline.
scripts/detect-releases.ts diffs the live GitHub releases against content/<product>/index.json. It can miss a release if:
- The release was published as a plain git tag without a GitHub Release entry (no
bodyto fetch). - The product's
index.jsonwas manually bumped without a real generation run. - GitHub's API is rate-limiting.
Workaround: dispatch daily-content.yaml manually with explicit product=<slug> version=<v> (see Dispatching a generation manually). The orchestrator does the right thing even when the release body is empty (it falls back gracefully).
If a release pack merges and the content reads badly:
- Easiest path: open a follow-up PR that edits the files in place. Cloudflare Pages rebuilds on merge to
main. No special procedure. - Revert the merge commit:
git revert <merge-sha>, push tomain, Pages rebuilds and the pack disappears from the file viewer. - Surgical: delete just the offending files (
rm content/<product>/<version>/channels/<file>.md), commit, push. The file viewer reflects whatever's on disk.
There is no "unpublish" step beyond editing the source. Nothing is posted automatically (planning §14), so a bad pack never reaches an audience until a human copy-pastes it.
Quick reference for the common symptoms:
| Symptom | First place to look |
|---|---|
Workflow hangs at attempt 1/N |
Did Nanocoder get a TTY? Check the script -qfec invocation in runNanocoder (scripts/generate-content.ts). Trust prompt? Confirm --trust-directory is in the spawn argv. |
gh pr create fails |
Org-level "Allow Actions to create PRs" toggle on https://github.com/organizations/Nano-Collective/settings/actions. |
Validator reports file-exists for files you can see |
Path-shape mismatch. Check the agent's prompt for the expected paths and the expectedFiles function in scripts/validate-content.ts. |
| Cloudflare Pages bot comments on PRs | Pages project has a Git connection. Disconnect or restrict preview branches. |
min-words failure on a thin patch release |
Either flesh the body out, or lower min_words for that channel in config/channels.json / config/team.json if the floor is genuinely wrong. |
TypeScript fails on _repos/<product>/... files |
tsconfig.json exclude list missing — re-add _repos, _refs, content/_local, content/_test. |
| Sign-out button missing on the live site | The iss field isn't in the get-identity response. Check the Access app's claims config. |
| Sign-out button 404s | The per-app /cdn-cgi/access/logout is being served by Pages's 404. The Navbar derives the team-domain logout from iss instead — confirm the user actually has an identity (the button is hidden when not). |