Releases: KingPin/Garrul
Release list
v1.22.1
Fixes: /admin/usage rejected valid Cloudflare API tokens.
If your usage dashboard showed "The CF_API_TOKEN currently configured did not validate" with http_401, this release is the fix — and your token was almost certainly fine.
Cloudflare issues two kinds of API token, each verifiable only at its own endpoint. Garrul checked /user/tokens/verify exclusively, so account-owned tokens (the cfat-prefixed kind the current dashboard hands out) always came back 401, no matter how valid or correctly scoped they were. The page failed closed before ever reaching the analytics queries, which would have worked.
Garrul now probes /accounts/{id}/tokens/verify first and falls back to the user endpoint, so both token kinds verify. If both probes fail, the error names each one instead of showing a single ambiguous code.
Notes:
- No config changes. No new env vars, no migrations — deploy and reload
/admin/usage. - If you re-created a token while chasing this, the old one likely still works; nothing here requires rotating it.
- The token-error page previously blamed a wrong
CF_ACCOUNT_IDor a dropped scope. Neither can cause it: the account endpoint verifies the bearer alone and ignores the ID in its path, so a wrong account ID passes verification and instead surfaces as a per-panelaccount_not_found. The copy now says so.
v1.22.0
Highlights
- Four anti-spam / editing dials are now tunable from the admin panel — edit window, spam link threshold, honeypot minimum fill time, and first-comment moderation. Each resolves DB override > env var > built-in default, so your existing
wrangler.tomlvalues keep working as the deploy-time default and "Reset to defaults" hands control back to the env. Moderation tab gains an "Anti-spam heuristics" card; the four rows they replace leave the read-only Configuration table. setup.shno longer misassigns KV namespace ids. Ids were substituted into the first remaining placeholder, so if you had reordered or hand-edited the[[kv_namespaces]]blocks in yourwrangler.tomland re-ran setup, every id could land under the wrong binding — setup reported success while the Worker read sessions out of the rate-limit namespace. Ids are now matched onbindingname, and a block with no matching binding warns loudly instead of touching an unrelated one.setup.shruns on macOS/BSD. The D1 id substitution used GNU-onlysed -i, which on BSD sed read the expression as a backup suffix and failed withinvalid command code, leaving the placeholder in place. No in-placesedremains in the script.
Notes
- The default edit window was split 5 minutes (widget) vs 15 (server); both are now 15. A window of
0disables editing outright. SetEDIT_WINDOW_MINUTESexplicitly if you were relying on the old widget-side value. SPAM_LINK_THRESHOLDstays tri-state:-1disables the check,0flags any comment containing a link,Nflags more than N.- Enabling the honeypot fill-time dial without
SPAM_FORM_TS_SECRETnow shows an inline warning in the admin UI rather than leaving the check silently inert. wrangler.tomlwrites go through an atomic rename, and setup's "Next steps" list now names all four placeholder vars (ALLOWED_ORIGINS,ADMIN_EMAILS,PUBLIC_BASE_URL,OAUTH_CALLBACK_BASE) instead of three.- No new env vars, no new secrets, and no D1 migration. Upgrading is a redeploy.
v1.21.0
Config lived in six hand-maintained lists that drifted apart: secrets.example.env, .dev.vars.example, wrangler.example.toml, scripts/setup.sh, the AGENTS-OPERATE §5 table, and release-manifest.json. This release makes one registry the source of truth for all 53 entries and adds a bulk path for setting secrets. Closes #42.
Highlights
- Bulk secrets.
cp secrets.example.env secrets.env, uncomment what you have, thennpx wrangler secret bulk secrets.env && rm secrets.env— one call instead of ~24 interactivewrangler secret putprompts.secrets*.envis gitignored. Leave anything you don't have commented: wrangler treats an uncommented empty value as a real, empty secret and will overwrite a live key with nothing. scripts/setup.shnow offers bulk or one-prompt-per-secret, and covers all 23 secrets. It previously prompted for 16 —CF_API_TOKEN,GITHUB_TOKEN,SPAM_FORM_TS_SECRETand theAKISMET_*pair were never offered. The six ID+secret pairs (Turnstile and the five OAuth providers) collapse behind one prompt each; Telegram's two secrets prompt separately, since the bot token alone enables outbound notifications.- One registry.
scripts/config-registry.tsholds all 53 entries (23 secrets, 30 vars).secrets.example.env,.dev.vars.example,wrangler.example.toml, setup.sh's prompt lists and the AGENTS-OPERATE §5 table are generated bynpm run config:build;npm run config:checkfails CI on drift. - New optional secret:
GITHUB_TOKEN— a read-onlypublic_repotoken that raises the 60 req/hr GitHub API cap used by update checks. Nothing breaks without it.
Fixes
npm run upgradenow reports optional secrets added since your installed release. They were previously invisible, passing neither the required-only secret diff nor the vars-only "new settings" filter.- "New settings" is keyed on
addedInrather than a manifest set-difference, so upgrading from an older install no longer lists settings you already have. wrangler.example.toml's[secrets] required = [...]block ships commented out, with the reason inline: declaring it makeswrangler devbind only the listed names out of.dev.varsand silently drop every other secret, so local OAuth, Resend, Akismet and Telegram read as unconfigured..dev.vars.exampleagain points at Cloudflare's always-pass Turnstile test keys for local dev.GITHUB_TOKENis declared in theBindingstype.
Notes
- No D1 migrations and no new required secrets — upgrading is redeploy-only. Run
npm run upgradeto see the plan for your install. - The Node.js 24 floor from 1.20.0 still appears in the upgrade plan's breaking-changes section. If you are already on 1.20.0 or later there is nothing to do; the entry is not yet version-scoped.
v1.20.0
Requires action before upgrading
Node.js 24 is now the minimum. Building or deploying on Node 22 fails at npm ci. Upgrade your build/deploy host first — the repo's .nvmrc now reads 24:
nvm install 24 && nvm use 24
npm ci # re-run after switching; a node_modules built on 22 can carry incompatible binariesnpm run upgrade prints these steps for you. No D1 migrations, no new secrets, and the renderer version stays at 1 — nothing to re-render, and rolling back means redeploying the previous tag.
Security
npm audit goes from 6 vulnerabilities (5 high, 1 moderate) to 0. The high-severity sharp/miniflare/postcss chain entered through the old vitest-pool-workers and wrangler; the three moderate advisories were in hono and are cleared by 4.12.32. Only hono, marked, and wrangler's bundler affect the deployed Worker — the rest are build-time only.
Dependencies
hono4.12.25 → 4.12.32,marked18.0.4 → 18.0.7wrangler4.106 → 4.115,typescript5.9.3 → 7.0.2 (the native port; zero source or tsconfig changes)@cloudflare/workers-typesv4 → v5 (v4 froze upstream),vitest-pool-workers0.16 → 0.19,@types/node→ 24 to match the engine floor,vitest4.1.7 → 4.1.10,tsx4.22.3 → 4.23.1
CI
Every workflow had been dark. All three gated their jobs on github.event.repository.private == false, which became a permanent no-op once the repo went public, and agents-docs-sync.yml additionally had no workflow_dispatch, so its gate was unreachable by any route. Guards removed, dispatch added, checkout pinned to @v6.
Documentation
Fifteen corrections from a full read pass against source. The ones that change what an operator can actually do:
- All five OAuth providers are now documented.
AGENTS.md,AGENTS-OPERATE.md, andINSTALL.mdall claimed GitHub + Google only, and theAGENTS-OPERATEsecret table omittedFACEBOOK_*,TWITTER_*, andDISCORD_*entirely — those providers have shipped since v1.13.0 but were undiscoverable to anyone following the guide. Setup walkthroughs, scopes, and callback URLs added for each; note the X provider slug istwitter, since it's the stored value inusers.provider. /admin/settingsis editable, not read-only. It has persisted feature flags and display/pagination numbers to thesettingsD1 table since v1.10.0 — no redeploy needed. The docs still sent you towrangler secret putfor everything.- The comment-tree cache is the Cache API, not KV (
docs/troubleshooting.md). It moved after a KV write-cap incident. The 60s TTL and the absence of global invalidation are now documented, so the up-to-a-minute delay before a moderation action reaches anonymous viewers is expected behavior rather than a bug report. comment.reported— the sixth webhook event — was missing fromdocs/webhooks.md.TURNSTILE_SECRET_KEY→TURNSTILE_SECRETinexamples/README.md(the wrong name simply doesn't work), andIP_HASH_SECRETis HMAC-SHA-256, not BLAKE3.
Contributor-facing: CLAUDE.md and CONTRIBUTING.md claimed tests run against Miniflare — they run in Vitest's plain node pool against hand-rolled in-memory D1/KV stubs. The README's widget size is now the measured 11.53 KB gzipped against the CI-enforced 20 KB ceiling.
Full Changelog: v1.19.1...v1.20.0
v1.19.1
v1.19.0
Editing a comment now shows the original text instead of a blank box.
feat
- New
GET /api/v1/comments/:id/sourceendpoint returns a comment's raw markdown to its author, gated exactly like the edit PATCH (author-only, within the edit window).
fix
- The widget's edit form prefills with the original markdown instead of opening empty, so authors no longer retype from scratch. Save stays disabled until the source loads.
Notes
- No new env vars — reuses the existing
EDIT_WINDOW_MINUTES. - No database migration. No breaking changes;
npm run upgradeis a straight pull.
Full changelog: v1.18.0...v1.19.0
v1.18.0 — Telegram operator integration
Telegram operator integration — get pinged, moderate, and query Garrul from Telegram.
Highlights
- Telegram outbound adapter — pick
telegramin/admin/webhooksand store a chat id. Notifications arrive as messages with inline moderation buttons, reusing the existing per-event filter + retry queue. - Interactive moderation — approve / spam / restore / delete / ban author / resolve reports straight from a button tap, through the same audited path as the dashboard (actions are attributed to your linked operator id). Buttons reflect the comment's current status, not the event, so flagging spam sends back a fresh "Not spam" message and vice-versa.
- Slash commands (read-only) —
/queue,/stats,/comment <id>,/user <id>. - Optional daily digest — a once-daily operator summary on the existing
*/15cron.
Notes for upgraders
- Off by default. Nothing changes unless you set
TELEGRAM_BOT_TOKEN. Inbound buttons/commands additionally needTELEGRAM_WEBHOOK_SECRET+ a one-timesetWebhookcall.TELEGRAM_BOT_USERNAME(plain var) enables the one-tap link on/admin/telegram. - New migration
0014_telegram.sql(telegram_links) — additive. Runnpm run migrate(or-- --remotefor prod). - No breaking changes. Existing generic/Slack/Discord webhooks are unaffected.
- Setup guide:
docs/telegram.md.
What's Changed
Full Changelog: v1.17.0...v1.18.0
v1.17.0
Moderation, thread lifecycle & community tools. All new behavior is optional and defaults off.
Highlights
- Reader reporting — readers can flag a comment from the widget (anon-allowed, no Turnstile). New admin Reported queue tab with an open-count badge and one-click Dismiss. Fires a new
comment.reportedwebhook. - Per-post close + auto-close — freeze a thread from the queue, or auto-close by instance sunset date or comment age. Closing blocks new comments/replies only; existing comments, reactions and votes stay live. Evaluated lazily at read/write — no cron.
- One-click ban-author — ban a comment's author from the queue (admin-only; records the originating comment in audit meta, with a CGNAT caveat for anon authors).
- Comment draft autosave — client-side
localStorage, restored into an empty field, cleared on submit/cancel. - Community auto-collapse — reversible, client-side fold of low-scored comments once a vote floor is met. Purely cosmetic; content stays in the payload.
Operator notes
- Migration required: run
npm run migrate.0013addsposts.closed,posts.published_atand thereportstable. Forward-only, no renderer bump. - New optional env vars (default off):
AUTO_CLOSE_DAYS,AUTO_CLOSE_AT,COMMUNITY_MIN_VOTES,COMMUNITY_COLLAPSE_RATIO— overridable at runtime from admin Settings. Seewrangler.example.toml+AGENTS-OPERATE.md§5. - New host attribute
data-publishedanchors age-based auto-close (AGENTS.md). - New webhook event
comment.reported— selectable per-endpoint in admin Webhooks; all-events endpoints receive it automatically.
Full Changelog: v1.16.1...v1.17.0
v1.16.1
Patch release — widget anti-spam fix.
Fixes
- Reply-form honeypot is hidden again. The offscreen-hiding CSS was scoped to the top-level form only, so the reply form's hidden
websitehoneypot rendered as a visible empty input below the reply box. Besides being a stray field, it exposed the anti-spam trap to bots (they could see which field to leave blank). The rule now applies to every form.
Upgrade notes
- No env var, config, or migration changes.
- Redeploy the Worker (
npm run deploy) to ship the rebuiltembed.js; the fix is in the embed bundle, so embeds pick it up on next load once deployed.
Full Changelog: v1.16.0...v1.16.1
v1.16.0
Richer, actionable webhook notifications for Slack & Discord.
Highlights
- Discord embeds. Comment notifications are now rich embeds — commenter (with avatar when available), the post title linked to the page, an event-colored accent (new = blurple, approved = green, spam/deleted = red, edited = grey), and a Links field.
- Clickable links on both adapters. Discord and Slack notifications now carry 🔍 Open in admin (jump to the moderation detail page) and 🌐 View page.
Notes for operators
- Admin links are built from
PUBLIC_BASE_URL(already a documented var — no new config required). If it's unset, the admin link is simply omitted and the notification still sends; posts with no/invalid URL omit the page link. - Navigation only — no moderation-from-Discord, by design.
- Security: comment-body injection is neutralized server-side — masked links (
[x](url)) are escaped,@everyone/role mentions are defused, and every clickable URL is server-generated or scheme-validated tohttp(s)only.
No migrations, no new secrets. Drop-in upgrade.
Full Changelog: v1.15.0...v1.16.0