Skip to content

P0: recover #2 organic page from a 404, fix unbootable published configs + broken sample code - #498

Merged
pftg merged 10 commits into
masterfrom
fix/live-post-defects
Aug 20, 2026
Merged

pftg merged 10 commits into
masterfrom
fix/live-post-defects

Conversation

@pftg

@pftg pftg commented Aug 20, 2026

Copy link
Copy Markdown
Member

Supersedes #493 (which was blocked by a stray commit from another session — see below). Same work, clean base off current master, plus two live defects found after it was opened.

P0 — the site's #2 organic page was a 404

Google ranks /blog/falcon-web-server-async-ruby-**in**-production/ at 37 clicks / 3,778 impressions / position 10.1 over 90 days — second only to the homepage — and that slug has never existed in this repo. Every click on our best-ranking blog result hit an error page, and the whole falcon * head-term cluster sits there.

Fixed with a Hugo aliases: entry (redirect verified in built output; correctly excluded from the sitemap). A reviewer swept all 450 ranking /blog/ URLs against published output — this is the only 404.

P0 — published boot configs that cannot boot

falcon --config config/falcon.rb serve appeared seven times on that page. It is invalid CLI — it errors Could not parse token "serve" because -c belongs to the subcommand. Two of the seven were the systemd ExecStart and the Dockerfile CMD. All replaced with falcon host, which reads falcon.rb from the app root; the multi-word env shebangs (which also fail on Linux) became falcon-host.

Broken copyable code in a post shipped this morning

multi-agent-llm-rails-rubyllm demonstrated model { ... } and claimed the block resolves lazily. In ruby_llm 1.16.0 model takes no block: with a nil argument it assigns an empty hash to @chat_kwargs, so the block form wipes the model and provider and the agent silently falls back to the configured default. Replaced with plain values and an explanation of the trap. Also cut "answers in milliseconds on a laptop" — the exact claim a sibling post dropped as unsupported — for the sourced 523MB figure.

Fabricated Rails config removed

Verified against Rails source, not memory:

Snippet Reality
async_query_executor = :fiber_pool Not a valid value — only nil, :global_thread_pool, :multi_thread_pool
allow_concurrency = true A no-op; only an explicit false does anything (inserts Rack::Lock)

Corrections to my own earlier claims

  • The isolation_level guidance in both the Falcon page and the fibers post told readers to add a line Falcon's Railtie sets unconditionally. Corrected in both; Railtie links pinned to v0.57.0.
  • An earlier commit claimed the external cover_image broke og:image. It didn't — metatags.image resolves first. Retracted in the doc; the frontmatter change stays as hygiene with cover_image_alt added.
  • §12c no longer certifies work that hasn't happened (three defects remain live in a section being rewritten separately, and the doc now says so).

Why this supersedes #493

A worktree collision landed another session's post (12e2ab1ac) inside #493, and my session lost write access to that branch before it could be dropped. This branch is that work cherry-picked onto current master without the stray commit, so the other session's post stays owned by its own branch.

Gates

bin/hugo-build green · check-post-visuals at floor (72) · alias redirect verified in built output · content-only diff, so visual suites correctly skipped.

🤖 Generated with Claude Code

pftg and others added 10 commits August 20, 2026 20:26
…id falcon CLI)

Two classes of defect, both verified by execution rather than reading.

1. multi-agent-llm-rails-rubyllm shipped a sample passing a block to
   `model` and claimed it resolves lazily. In ruby_llm 1.16.0 `model`
   takes no block: with a nil argument it assigns an empty hash to
   @chat_kwargs, so the block form WIPES the model and provider and the
   agent silently falls back to the configured default. Only
   instructions/tools/schema take blocks. Replaced with plain values
   plus an explanation of the trap. Also cut "answers in milliseconds
   on a laptop" - the exact claim a sibling post dropped as unsupported
   - in favour of the sourced 523MB size.

2. falcon-web-server-async-ruby-production shipped
   `falcon --config config/falcon.rb serve` seven times. That is invalid
   CLI: it errors "Could not parse token serve" because -c belongs to
   the subcommand. Two of the seven were the systemd ExecStart and the
   Dockerfile CMD, so the page published boot configs that cannot boot,
   on the site's #2 organic page. All replaced with `falcon host`, which
   reads falcon.rb from the application root, and the multi-word env
   shebangs (which also fail on Linux) with falcon-host.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…age was external

Paul asked to schedule a Puma->Falcon migration post. Premise audit says
write it INSIDE the page that already ranks, not as a new URL:
falcon-web-server-async-ruby-production already carries a ~200-line
'## Migration from Puma/Unicorn' section, is the site's #2 organic page,
and its cluster sits at position 4.8 for 'falcon ruby' (206 impr/90d).
Zero 'puma to falcon' queries drew impressions in 90 days, so there is
no separate intent to capture - a second page would only split a
position-4.8 cluster (the same §4 defect that forced the Rails 8 auth
consolidation). Recorded as R9 + §12a with the scope for the rewrite.
Paul confirmed the approach same day.

Also fixes a live STEP 6b violation found during the audit: that page's
cover_image pointed at an external practicaldev/Cloudinary URL while an
unused local cover.png sat in the bundle - og:image on our #2 page
depended on a third-party CDN. Now resolves to the Hugo-processed local
asset (verified in built output).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…dit record

4-eyes review caught that I attributed the Falcon cluster's rankings to
the wrong URL, and in doing so surfaced a much bigger defect than the
one the audit set out to fix.

Google ranks /blog/falcon-web-server-async-ruby-IN-production/ at 37
clicks / 3,778 impressions / position 10.1 over 90 days - the site's #2
organic page - and that slug has never existed in this repo. It returns
404 with no redirect, so every click on our best-ranking blog result
hits an error page, and the whole 'falcon *' head-term cluster sits
there. The live post (no 'in') records zero GSC data, which is exactly
the signal I dismissed as odd earlier in the audit.

Fix: aliases: ["/blog/falcon-web-server-async-ruby-in-production/"] on
the live post - Hugo emits the redirect (verified in built output). Live
slug keeps its name; every internal link, including the three posts
shipped today, already points at it. Swept the other top-10-by-clicks
and highest-impression pages for the same drift: none found, so this is
isolated.

Also corrected in §12a/§12b: the claim that the external cover_image
broke og:image was WRONG - metatags.image resolves first and was already
cover.png, and an absolute URL passed to Resources.Get can never win, so
the live og:image was already local. Kept the frontmatter change as
hygiene, added cover_image_alt, dropped the inert dev_to_id: 1
template artifact, and softened the 'zero migration queries' claim
('falcon vs puma' / 'puma vs falcon' each drew 1 impression).

Reviewer: core-reviewer 4-eyes pass, verdict FIX-FIRST; all P0-P3
findings applied. Every claim re-verified by curl + per-page GSC pull.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-URL sweep

Two invented config snippets on falcon-web-server-async-ruby-production,
both verified against Rails source before touching:
- async_query_executor = :fiber_pool - not a valid value (only nil,
  :global_thread_pool, :multi_thread_pool exist, per active_record.rb)
- allow_concurrency = true - a no-op with a wrong comment; only an
  explicit false does anything (inserts Rack::Lock, per railties
  default_middleware_stack.rb)
That block now carries just isolation_level = :fiber with an accurate
comment. A third fabrication (async: true in database.yml, not a real
HashConfig key) sits inside the 580-786 window being rewritten on
upgrade/falcon-puma-migration-runbook and is fixed there instead of
conflicting. Filed as §12c with the generalization: this is a dev.to-era
import ranking on head terms whose config examples were invented, so the
de-fabrication sweep should extend to the rest of the imported cluster.

Also strengthened §12b: a reviewer swept all 450 ranking /blog/ URLs
from the 90d GSC report against published output - the falcon 404 is the
only one, so 'isolated' now rests on the full population rather than a
top-N sample. Recorded the methodology correction too: do NOT diff GSC
URLs against ls content/blog/ (frontmatter slugs differ from directory
names - 17 false positives to 1 real hit here); the check must hit built
output or curl.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…'t tell readers to add it

Source verification for the Falcon migration rewrite turned up that
socketry/falcon ships a Railtie setting
config.active_support.isolation_level = :fiber unconditionally
(lib/falcon/railtie.rb, no guard or version check). The post I shipped
this morning framed it as one of 'two settings that matter' and handed
the reader a line to add - inaccurate under the very server the post
recommends. Reframed as 'you get this for free, here is what it does',
with the Railtie linked, and the stale 'with isolation_level = :fiber
set' instruction dropped from the closing advice. The pool: guidance,
which IS the reader's job, now stands on its own.

Caught by the upgrade coordinator's research pass, verified against
Falcon source before editing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…d corrections

Source verification found two more beyond the three already logged: an
outdated Falcon config DSL (load :rack / rack hostname do, superseded by
require falcon/environment/rack + service ... include
Falcon::Environment::Rack) and an unsupported 'no explicit timeout
needed as fibers are cooperative' claim. Both sit inside the section
being rewritten and are fixed there.

Also recorded the two findings that inverted assumptions, each verified
against primary sources before being written down:
- Thread#[] is FIBER-local, not thread-local (Ruby docs: 'each fiber has
  its own bucket'); thread_variable_get/set is the thread-local pair. So
  'move thread-local state to fiber-local' is backwards for the API
  people actually use. Real failure modes are the reverse:
  thread_variable_set leaks request A's data into request B, while
  Thread.current[:cache] silently never hits.
- Falcon's Railtie sets isolation_level = :fiber unconditionally, which
  is why the fibers post shipped today needed its correction.

Sharpened the generalization to a P1: five invented/invalid/stale
examples on one ranking dev.to-era import means the imported technical
cluster needs the same de-fabrication sweep, config examples first.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The upgrade writer caught that line 276 of this page still told readers
to add config.active_support.isolation_level = :fiber and called it
'Required under Falcon' - the exact defect I had just corrected in the
fibers post, left standing inside my own rewrite of that block. Falcon's
Railtie sets it unconditionally, so the page was about to contradict its
own new migration section.

Now reframed to match: the Railtie link, an explicit 'you do not add
this', what the setting actually governs, and pool: named as the
reader's real job. Both posts and the incoming migration section now
agree.

Worth recording as a pattern: fixing an instance of a defect is not the
same as fixing the defect. When a claim turns out wrong, grep the page
you are editing for it before moving on - I fixed a sibling post and
walked past the same sentence three lines from my own edit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Independent build-verified cross-check raised a coordination risk: two
branches edit the same top-ranking file (this one touches frontmatter +
the Rails Integration block, upgrade/falcon-puma-migration-runbook
rewrites the migration section) and my commits had already shifted the
other branch's line numbers twice. Recorded the resolution: this branch
merges first because it carries the 404 recovery, the upgrade branch
rebases after. Told that agent directly too.

Replaced the hard-coded 580-787 references with 'find it by heading, not
by number' - they had already gone stale twice in one session.

Also recorded what the same check verified, since it corrects an earlier
assumption of mine: cover_image never drove the rendered hero (the theme
resolves it via .Resources.GetMatch "cover.*") nor og:image (which comes
from metatags.image), so the frontmatter change is accessibility-only -
the alt attribute - with zero pixel change and no visual-suite risk. The
alias emits meta-refresh + canonical to the live URL and is excluded
from the sitemap, so no duplicate-content signal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Flagged by the upgrade writer as sitting just outside its editing
window. Same intensifier the batch sweep removed from four posts this
morning - it was pre-existing here, which is a small confirmation that
the tell predates today's batch rather than being introduced by it.
One word, no meaning lost.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codex cross-check BLOCKED this branch on a real defect in my own record.
§12c said three remaining fabrications 'are fixed there' on
upgrade/falcon-puma-migration-runbook. That branch has zero commits and
is unpushed - verified. So this PR would have routed 3,778 monthly
impressions onto a page while the document told a cold session the page
was clean. Now states plainly that the three are LIVE, that the branch
has not started, and 'do not read this as an all-clear'. Same fix for a
'both applied' heading that covered a Thread#[] correction which has not
been applied to any live line yet.

Content fixes from the same review:
- The rewritten block contradicted itself one sentence later: 'the one
  setting that matters' followed immediately by naming a second setting
  as the reader's job. Now 'the setting most migration guides tell you
  to add', with pool: given its own paragraph.
- 'you do not add this' was overstated for the workflow this same page
  prescribes: it puts falcon in the production group, so in development
  the Railtie never loads and isolation_level stays :thread. Caveat
  added.
- Railtie links pinned to v0.57.0 in both posts - blob/main can move
  under us, and the page pins a gem range two lines away.
- Recorded a sixth defect of the same class found on a later pass:
  after_connect: with a Ruby logger call in a second database.yml block,
  also not a real key.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d32c15a5-ab7d-4adf-8518-90b3594da246


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pftg
pftg merged commit 349185a into master Aug 20, 2026
5 checks passed
@pftg
pftg deleted the fix/live-post-defects branch August 20, 2026 19:03
pftg added a commit that referenced this pull request Aug 20, 2026
#498 and this branch both edited the migration section of the same post.
#498 landed first, replacing seven invalid `falcon --config ... serve`
invocations with `falcon host`. This branch had already deleted that
whole section and rewritten it as a runbook, so git matched three
orphaned fragments of the removed text against the new prose and
conflicted on them:

  # config/falcon.rb for staging   /   # After: config/falcon.rb
  #!/usr/bin/env falcon-host

All three are remnants of text this branch removed on purpose, so the
resolution takes HEAD in each. Nothing from #498 is discarded by that -
its edits outside the rewritten section merged cleanly.

Verified after resolving, since a bad merge here would silently restore
a defect we just shipped a P0 for:
- zero conflict markers
- zero `falcon --config` (the unbootable invocation #498 removed)
- zero `:fiber_pool` / `allow_concurrency` (the fabricated configs)
- aliases: still carries the in-production 404 recovery
- preload "config/environment" still present in all four places
- the native-extension check is still the Gem::Specification command,
  not the `grep "require" Gemfile` that returned the wrong gems
- code fences balanced (86)
pftg added a commit that referenced this pull request Aug 20, 2026
…0% CTR) (#506)

* fix(content): the Kamal 2 guide was documenting Kamal 1

The published post ranked page-1 for "kamal deploy minimum ram" (9.5),
its variant (9.3) and "kamal 2.0" (7.5) - and took 0 clicks on 32
impressions over 28 days. It earned the traffic while being wrong.

Verified against the real gem (kamal-2.12.0, fetched and unpacked), not
from memory. What the old post taught vs what Kamal 2 does:

  traefik: block in the flagship deploy.yml
    -> Traefik is REMOVED in Kamal 2. cli/proxy.rb:151 cleanup_traefik,
       main.rb:204 "replace Traefik with kamal-proxy",
       configuration.rb:383 rejects pre/post-traefik-reboot hooks.
       Correct config is proxy: ssl/host.

  top-level healthcheck: with port:/max_attempts:
    -> lives under proxy: with interval/path/timeout
       (configuration/docs/proxy.yml:117)

  .env.erb + .env secrets model
    -> kamal init creates .kamal/secrets (cli/main.rb:159-163).
       The old post never mentioned .kamal/secrets once, across 5 places
       that taught .env. The whole secrets workflow was a major version
       behind.

  bin/kamal "created by init"
    -> only with --bundle, which defaults false (cli/main.rb:147)

  strategy: blue_green / max_surge / max_unavailable / deployment:
    -> ZERO matches anywhere in the gem. Invented config. Kamal's real
       rolling deploy is boot: limit/wait.

  "Ruby 2.7+ required" (EOL 2023), "Ubuntu 20.04+" (EOL Apr 2025)

A reader pasting the old flagship deploy.yml got a deployment that does
not work. That is the same defect class as the P0 fixed in #498.

Also fixed, per review:
- duplicate H1: single.html:47 already renders <h1>{{ .Title }}</h1>,
  and the body repeated the title verbatim - two identical H1s
- repetition: config.force_ssl taught 3x (221/589/661), production.rb
  edited across 5 blocks, 3 overlapping performance sections, 3 closing
  sections
- unsourced cost table ($155/mo, "54% savings", "75% savings") - deleted
  rather than re-sourced; it was a digression in a deploy tutorial
- dev_to_id: 1 placeholder (same field #498 stripped from the Falcon post)
- flat file -> page bundle so cover.png can be a bundle resource

URL is preserved. The old file had no slug:, so Hugo derived the permalink
from the title; an explicit slug: now pins the same URL while the title
drops the stale "in 2025" that was showing in the SERP.

Scope: this post owns first-deploy intent (setup, install, rollback, RAM).
kamal-2-multi-server-deployment-complete-guide (2026-08-07) already owns
multi-server correctly, so roles, boot limits and multi-host TLS hand off
to it instead of being re-taught here. Found via qmd, which surfaced the
content-plan row showing both target "kamal 2.0".

Still to come on this branch: cover.png, pre-rendered mermaid SVG, gates.

* feat(content): cover + pre-rendered mermaid for the Kamal 2 guide

Cover follows the 6-slot house layout. Chips carry the post's three
findings rather than decoration: kamal-proxy not Traefik, .kamal/secrets,
build runs on your laptop. Status reads KAMAL 2.12.

First render wrapped hero line 3 ("Rails on your own / servers") and
stranded one word on its own line, turning a 3-line template into 4.
Shortened to "Your own servers" and re-rendered. Scored after looking at
it: look OK, readable without zoom, earns the scroll (the Traefik chip
tells you something), chips helpful not decorative.

Mermaid pre-rendered to SVG at authoring time per bin/render-mermaid, so
the page ships no mermaid.js. Vertical 4-node chain keeps viewBox at
272x563 - 12px min font renders ~17.2px at 390px wide, clear of the 9px
floor, and 563px tall is under one mobile viewport so it is not a wall.

bin/hugo-build: 8/8 validators, 1174 pages. URL preserved at
/blog/kamal-2.0-complete-rails-deployment-guide-deploy-without-heroku-in-2025/.
Post now emits one H1 (the theme's), matching the clean sibling post - the
site-wide "Welcome to JetThoughts" hero H1 is pre-existing on every page
and is not touched here.

* fix(content): three fabricated "silent failure" claims, caught by running kamal

A 4-eyes reviewer RAN `kamal config` against the post's own deploy.yml
instead of reading the gem, and falsified the article's opening thesis.
I re-ran all three myself before fixing.

1. The hook was inverted. I wrote that pasting a `traefik:` block into a
   Kamal 2 config produces no warning. It produces:
     ERROR (Kamal::ConfigurationError): unknown key: traefik
   Kamal validates top-level keys and refuses. My error was reading
   Validator::Configuration#allow_extensions? => true and concluding
   unknown keys pass - that flag is for YAML extension keys, not
   arbitrary config keys. The same read-source-assert-behavior mistake
   this post exists to correct.

2. Same for a top-level `healthcheck:` block - also rejected. The old
   wording contradicted our own multi-server post, which correctly says
   Kamal rejects unrecognized keys at validation time. That post was
   right and this one was wrong.

   Both now show the real error text, and the section turns the
   validator into the porting tool it actually is.

3. `kamal config` does not print secrets or env. configuration.rb#to_h
   emits roles/hosts/image/builder/accessories only; the gem's own desc
   string ("including secrets!") is stale and I inherited it. Verified:
   mistyping a secret name leaves output byte-identical and exit 0. The
   post told a stalled reader to run a command that cannot show what it
   claimed - worse than no advice at all.

Also corrected against the gem:
- setup is server:bootstrap + deploy(boot_accessories: true); there is
  no env-push step (no cli/env.rb in 2.12)
- upgrade confirms ONCE, then runs both sub-upgrades with confirmed: true
- traefik reboot hooks should be RENAMED to (pre|post)-proxy-reboot, per
  the error's own text - "delete those files" threw away working hooks
- rollback gates on a container, not an image, and prune keeps 5 by
  default (configuration.rb:216) - that retention is the real limit on
  how far back you can roll
- the rollback example was a literal bracketed placeholder that errors
  when pasted; now a real SHA

Voice: dropped a first-person-singular "the question I get most" under a
Team byline, an "almost everyone" generalization, an unsourced
superlative, and a rule-of-three in twitter_description.

bin/hugo-build green. URL unchanged.

* chore(stitch): track the Kamal 2 cover source

.stitch/designs/ is gitignored, but the cover HTMLs that back published
posts are force-added by convention - falcon-production-tuning-cover.html
(this one's parent), rails-8-authentication, solid-queue-advanced and two
others are all tracked. Without the source, regenerating or tweaking this
cover means rebuilding the 6-slot layout from scratch.

* feat(content): add a copy-pasteable agent prompt for the Kamal 1 -> 2 port

Paul asked for a prompt a reader can hand to their AI agent instead of
doing the migration by hand.

The prompt's spine is that kamal config is a real validator: it raises
ConfigurationError on every unrecognised top-level key, so the agent gets
a loop it can close without human judgement. It is told not to declare
the port finished until that command exits 0.

Two guards matter more than the key mapping. First, most Kamal material
in training data predates Kamal 2, so the prompt explicitly forbids
relying on recall and points at kamal docs <section> - the reference that
ships inside the installed gem. Second, it forbids inventing keys, which
is the exact failure the post documents: the guide this replaces carried
strategy: blue_green and max_surge, neither of which Kamal has ever had.
An agent trained on pages like that will reach for them.

The prompt also refuses to touch credentials (names only, keep .env until
the human confirms), renames traefik reboot hooks rather than deleting
them, and stops before deploy/setup/upgrade so a person reads the diff
before anything restarts real infrastructure.

bin/hugo-build: 8/8 validators.

* chore(content): use the Sources heading in the Kamal guide

I wrote this post's citation list as a bare 'Further reading:' paragraph,
copying the form its RubyLLM neighbours used. That form is drift - the
repo's convention is '## Sources', which 4 posts already used and 16 more
are being converted to on chore/further-reading-typography.

Fixing it here so this post doesn't merge as the 17th instance of a
pattern being removed in parallel.

bin/hugo-build: 8/8 validators.
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.

1 participant