Fixes

Real bugs from real indie projects, traced to root cause and solved.

50 fixes · all from production

Payments & billing 2

Supabase & data 6

SupabasePostgres

The keepalive returned 200 every day and the database paused anyway

A daily cron pinged Supabase, got a 200 every single run, and the free-tier project still went to sleep twice. The endpoint was answered by the gateway and never reached Postgres, so the inactivity scan counted nothing. Here is the ping that actually executes SQL.

4 min read →

SupabaseGitHub Actions

The Supabase free-tier keepalive that actually works

A free-tier Supabase project pauses after a week idle, and the obvious keepalive cron still let it sleep. The reason: the request was getting a 401 at the gateway. Here is the endpoint that returns 200 with the anon key.

3 min read →

SupabaseRealtime

Supabase Realtime breaks on remount unless the channel is unique

A Realtime subscription works once, then throws or goes silent when the component remounts. The cause is two subscriptions fighting over the same channel name. Clean up on unmount, and make the name unique per mount.

1 min read →

SupabaseSecurity

Supabase signed URL vs public URL: keep the storage bucket private

getPublicUrl on a public bucket hands a permanent, unauthenticated link to anyone who sees it once. Keep the bucket private, scope the storage policy to auth.uid(), and serve files with createSignedUrl links that expire: 900 seconds in the app, 3600 for a deliberate share.

2 min read →

SupabaseGDPR

Account deletion that satisfies the app stores (and GDPR)

Apple and Google require in-app account deletion. The traps are the order you delete in and trusting a client-supplied id. Here's the Supabase version that doesn't orphan storage or delete the wrong user.

2 min read →

SupabasePostgres

Rate limiting in Postgres, no Redis

You don't need Redis or a third-party service to rate-limit a Supabase app. One table, one atomic upsert, and a security-definer function keyed to the user. Race-safe and impossible to bypass.

2 min read →

Cloudflare & deploy 10

TestingCI

ENOENT: scandir in CI, green on my machine, and the path was correct

A vitest suite passed locally on every run and failed the first CI job with ENOENT: scandir. The path was not wrong. The test read its fixture corpus by absolute path from a sibling repository, so a CI checkout of one repo could never contain it. The bug is a test depending on state outside the artifact under test.

3 min read →

CloudflareCloudflare Pages

Your _headers file serves 7 headers in production and 2 on a preview deployment

After adding a scoped Content-Security-Policy rule, the preview deployment served only two of seven headers. It looked exactly like a rejected _headers file. Deploying the unchanged original to a preview produced the identical result.

4 min read →

CloudflareCloudflare Pages

GET /_worker.js returns 200 and every route it declares 404s

A hand-written _worker.js deployed to Cloudflare Pages, the deployment reported success, the site worked, and the entire API was gone. The file had been uploaded as an asset instead of compiled as a worker, so Pages served the source code at its own path.

5 min read →

CloudflareCloudflare Pages

My Cloudflare Pages deploy returned success and served 500 on every route

A direct upload to Cloudflare Pages answered success: true, handed back a deployment URL, and every single path on it returned HTTP 500 - including the live domain, because a deploy with no branch named goes to production. The manifest is not the files. Here is the three-step upload that actually works.

4 min read →

CloudflareWindows

When wrangler dies behind a corporate proxy, use the API

Wrangler and other vendor CLIs hang or throw TLS errors behind a corporate proxy that intercepts HTTPS. Skip the CLI and drive the Cloudflare REST API directly with curl and a scoped token.

2 min read →

CloudflarePages

A Pages custom domain added by API gets stuck on 'CNAME record not set'

Attach a custom domain to a Cloudflare Pages project through the API and it can sit at pending forever. The dashboard auto-creates the DNS record; the API doesn't, and a Pages-scoped token can't either.

2 min read →

CloudflarePages

Your Cloudflare Pages env vars do nothing (Direct Upload vs Git build)

You set a PUBLIC_ variable in the Pages dashboard, redeploy, and the build still can't see it. The reason: a Direct Upload project builds in your CI, not on Cloudflare, so the dashboard env vars never reach the build.

2 min read →

CloudflareCI

Your Cloudflare build passes locally but silently fails in CI

A Vite alias that reached outside the repo root built fine locally but failed on Cloudflare Pages for weeks, while the old bundle kept serving.

2 min read →

ViteTypeScript

A stale vite.config.js silently overrode my vite.config.ts

Edits to vite.config.ts had no effect because a compiled vite.config.js sat next to it, and Vite loads the .js in preference to the .ts.

1 min read →

HostingWindows

catbox.moe invalid uploader (HTTP 412) and the free image host that worked

catbox.moe answered HTTP 412 "invalid uploader" and 0x0.st had uploads switched off, both from behind a corporate proxy on Windows. A public GitHub repo served through jsDelivr's CDN gave free image URLs that stay up.

2 min read →

Windows & tooling 13

PowerShellWindows

A log write killed the job it was logging, and the exit code still said 0

$ErrorActionPreference = 'Stop' is the setting every PowerShell guide recommends. It also promotes a failed Add-Content into a terminating error, so a file lock on the log file aborts the run from a line that does not matter, in the middle of work that does.

3 min read →

GitWindows

My editor said 10,000+ changed files and git status said 31

Both numbers were true, about different repositories. A shallow clone killed by a 60-second timeout left a repo with a valid HEAD, 6,981 files on disk, no .git/index at all, and an orphaned index.lock. With an empty index, every tracked path reads as a staged deletion.

4 min read →

WindowsAntivirus

IDP.HELU.PSE85: the antivirus killed my script silently, and it was right to be suspicious

A script that read a local credential file and POSTed it onward stopped running. No error, no exit code, no log line, no popup. It matched the behavioural signature of a token stealer, and the fix was to stop looking like one rather than to add an exception.

5 min read →

WindowsJSON

Two config keys differing only by a drive letter's case, and half your settings vanish

A tool registered a server, listed it as connected, and never loaded it. The config file held C:\Dev\Base and c:\Dev\Base as separate keys. One process wrote to one, the other read from the other, and both were behaving correctly.

5 min read →

WindowsEdge

msedge --headless --print-to-pdf exits 0 and writes no file

The command returns immediately, the exit code is 0, there is no error on stdout or stderr, and the PDF does not exist. A browser window was already open, and your invocation handed its arguments to that process and quit.

4 min read →

WindowsTask Scheduler

schtasks says the task is disabled, Get-ScheduledTask says Ready, seconds apart

AVG Gaming Mode mass-disables every Windows scheduled task and re-enables them minutes later. The trigger is not a game: browsers sit in the Gaming Mode app list, so any headless-browser automation does it to you.

6 min read →

DockerWindows

Docker Desktop -Shutdown exits 0, prints "backend already running", and shuts nothing down

The documented quit command returns success, leaves Docker running, and on one invocation spawned an extra 237 MB process. The flag is not parsed by recent builds, so it falls through to the default show-the-dashboard path.

4 min read →

AutomationPowerShell

My dashboard said zero for two months, and the number was never wrong

A daily digest reported zero pins published, every day, for sixty one reports in a row. The weekly review escalated it to zero activity, stalled, and that reached my decision log. Pins were publishing the whole time. The counter was reading three spreadsheets that had finished two months earlier, so zero was the only answer it could ever have given.

6 min read →

WindowsTask Scheduler

Scheduled task Last Result 2147020576 on a task that runs perfectly by hand

0x80070420 does not mean the task ran and failed. It means the trigger was missed because the machine was asleep, and Last Result is sticky, so a weekly task reports the same failure every day until its next run.

4 min read →

Claude CodePython

We decided it three weeks ago and I could not find it

Claude Code writes every session to disk as JSONL and nothing reads them back. So a decision, a number, or the reason you rejected something is gone in practice the moment it scrolls out of context, even though the bytes are sitting in your home directory. Here is what searching them actually takes, including the three things that make the naive version fail.

6 min read →

WindowsPython

DETACHED_PROCESS is why your background job pops console windows

A Python git hook flashed a black console window on every commit. The flag meant to hide it was causing it, but not in the way everyone says: DETACHED_PROCESS opens no window itself. It leaves the child with no console, so every console program that child runs gets a fresh visible one.

5 min read →

PowerShellWindows

PowerShell mangles quotes in CLI args, so pass JSON by file

A curl or gh command with inline JSON works in bash and cmd but the payload arrives corrupted from PowerShell. PowerShell rewrites embedded double quotes when handing args to native executables. Pass the payload by file or stdin instead.

2 min read →

PowerShellExpo

PowerShell globbing mangles Expo Router [id] file paths

Reading and writing files at bracketed paths like app/[id]/index.tsx failed because PowerShell treats [id] as a wildcard character class.

1 min read →

Marketing ops 1

More 18

Web ScrapingPython

My href regex found 0 links on a page with 101 of them

A link-extraction regex returned nothing on a page full of links. The page was fine. It ships minified HTML, where quotes around an attribute value are optional, so href=/media/x.pdf matches nothing that expects href="...".

5 min read →

SEOAstro

Search Console: "Page with redirect" on 106 pages that all return 200

An index-failure email listed 102 URLs as Page with redirect, and every one of them loaded fine in a browser. Astro's directory build format emits /about/ in the sitemap and rel=canonical, every internal href in the source said /about, and the host 308s between them. Googlebot only ever reached the site through a redirect.

6 min read →

AstroCSS

My scoped CSS was correct, the elements were correct, and none of it applied

A tool on this site shipped with every severity dot zero pixels wide and every badge a square block. The stylesheet said otherwise, the markup said otherwise, the page returned 200 and the console was clean. Astro scopes a plain <style> with an attribute it stamps on elements that exist at build time, and everything on that page is built at runtime with innerHTML.

4 min read →

AutomationAlerting

My alert was correct every morning for four weeks, and that is why I stopped reading it

A daily report told me to run three updates. It told me that in fourteen reports across twenty eight days, word for word, and it was accurate every single time. One of the three was genuinely worth doing and I never saw it, because the other two were a permanent false alarm produced by conditions the report had no vocabulary for.

5 min read →

ShellPython

IndentationError on a line that is a comment, because a backtick in it ran as a shell command

A git hook broke on every commit with an IndentationError pointing at a comment. The comment mentioned a command in backticks, the whole Python program was inside a double-quoted shell argument, and the shell had run the command and pasted its output into the source.

5 min read →

AIVerification

Every take passed the check and the voice still changed between lines

A generated narration changed speaker every few lines. The quality gate that existed to catch exactly that passed all 17 lines on the first take, word perfect. The gate scored transcript similarity, and a transcriber cannot hear pitch, so the defect sat in full view of a check that was structurally incapable of seeing it.

6 min read →

MCPClaude Code

MCP error -32000: Connection closed means your server crashed before it said hello

The client reports a transport error and nothing else. The server's real traceback went to a stderr nobody is showing you. Pipe a raw initialize handshake into the exact command from your config and the crash prints in your terminal.

5 min read →

AIAccessibility

I asked for 4.5:1 contrast and the model returned 2.59:1

The contrast requirement was in the prompt, stated as a number. One model returned an accent colour at 2.59:1 against the background it had just chosen, and on another run declared the scheme was light while giving a near-black background.

5 min read →

AITooling

My fix reverted itself after an update, four times, and the grep that guarded it missed a third of the damage

A settled configuration decision came back undone in eight files after a routine vendored-directory sync, and nothing noticed for two days. Three more instances of the same shape followed: a plugin cache, a set of generated git hooks, and a marketplace bundle. If your fix lives in a file someone else owns, it is not a fix, it is a lease.

5 min read →

CLIGumroad

The CLI printed "updated" on all 7 products and 2 of them did not change

A bulk description update reported success on every product. Re-reading the field showed the new text had not persisted on two of them. No error, no retry hint, no pattern. The exit code told me the request was accepted, not that the state changed.

4 min read →

Pythonzipfile

zipfile append mode corrupted four of six product zips and only testzip() noticed

Adding one file to six archives with ZipFile(path, "a") succeeded every time. The files opened, namelist() was correct, and four of the six were damaged. Only testzip() could see it, and the damage was in files nobody touched.

4 min read →

SVGReact

transform on a root <svg> in JSX does nothing, and nothing errors

A rotation prop rendered perfectly and never rotated. transform is an SVG presentation attribute, so it only applies to elements inside an SVG document. On a root <svg> in an HTML tree, the HTML layout box owns the transform and the attribute is dropped.

4 min read →

Open GraphCloudflare Pages

Link preview blank? Open Graph tags set by JavaScript never reach the crawler

Social crawlers read the HTML bytes and do not run your JavaScript, so og:title and og:image written client-side never reach them, even while DevTools shows the right tags. Check with curl and a crawler user-agent, then inject the tags server-side, here with a Cloudflare Pages Function.

5 min read →

YouTubeffmpeg

A faceless YouTube pipeline on a free local stack

Turn a script JSON into a captioned long-form video plus three derivative Shorts with no subscriptions, using edge-tts, headless Edge, and ffmpeg.

3 min read →

Gumroadfpdf2

Markdown to a sellable product PDF and a converting landing page

A config-driven Python pipeline that turns chapter markdown files into a styled A4 PDF and generates a self-contained Gumroad landing page, with no Windows font dependency and no API keys.

3 min read →

CSSHTML

The hidden attribute stops working when CSS sets display

A modal with the hidden attribute rendered open on every page load and refused to close. The cause: a one-line CSS display rule silently overrides the hidden attribute, and a one-line attribute selector fixes it.

2 min read →

CSSFlexbox

Align card footers to the bottom of a CSS grid with margin-top: auto

Cards in a row hold different amounts of text, so their prices and links sit at ragged heights. Make each card a full-height flex column and give the footer margin-top: auto. It needs no fixed heights or JavaScript.

2 min read →

SEO

Search Console rejects a bare sitemap filename

Submitting sitemap.xml to a Search Console domain property fails or just won't take. A domain property wants the full absolute URL, not the filename.

1 min read →