Error fixes

Paste your error, get a fix that was tested

Every fix here comes from a learned-failure record in one of my projects' Salvor memory: what broke, why, what fixed it and how the fix was checked. Names, paths and hosts are removed.

Share this page on X

Google OAuth refresh token dies every 7 days

Report agent memory ·

Symptom
A scheduled job worked for a week, then the token refresh returned HTTP 400 invalid_grant: "Token has been expired or revoked".
Cause
The OAuth consent screen was in Testing mode. In Testing, Google issues refresh tokens that expire after 7 days (refresh_token_expires_in = 604799).
Fix
Re-authorize to get going again. The permanent fix is to move the consent screen to Production (which may need Google's review). Make the job fail loudly with "re-authorization needed" instead of producing an empty report.
How it was checked
The new token's refresh_token_expires_in field; a reminder before day 7 while still in Testing.

Device-code (TV) OAuth client can't get the analytics scope

Report agent memory ·

Symptom
Authorization through the device-code flow worked for uploads but was refused for the YouTube Analytics read scope, whatever the consent screen said.
Cause
The "TV and limited input" client type cannot be granted sensitive scopes such as yt-analytics.readonly.
Fix
Create a second OAuth client of type Desktop app and use the loopback flow for analytics, with its own token file. Keep the device-flow client for what it can do.
How it was checked
Each token file only holds the scopes its client type can get; both jobs run with their own token.

MCP server fails to connect with CONNECT_TIMEOUT

Agent setup memory ·

Symptom
An MCP server failed almost every start with CONNECT_TIMEOUT, but started fine when run by hand.
Cause
It was launched with npx -y package@latest. npx checks the registry and may download and unpack the package before it runs, which can take longer than the client's connect timeout.
Fix
Install a pinned version once into a prefix you can write to, and point the config at that local binary instead of npx.
How it was checked
Several client restarts in a row, all connected. Note: a plugin sync can put the npx line back.

ffmpeg has no drawtext or libx264, and there's no sudo

Agent setup memory ·

Symptom
"No such filter: 'drawtext'" or "Unknown encoder 'libx264'". The only ffmpeg was a minimal build, and apt install needs root.
Cause
The bundled ffmpeg was built with --disable-everything. Installing a full one normally needs root.
Fix
Let apt list the packages with --print-uris (no root needed), download them with wget, unpack each with dpkg-deb -x into one folder, and point LD_LIBRARY_PATH at its lib directory.
apt-get install --reinstall -y --print-uris ffmpeg 2>&1 | grep -oP "^'[^']+'" | tr -d "'" > uris.txt
mkdir pkgs sysroot && (cd pkgs && wget -q -i ../uris.txt)
for f in pkgs/*.deb; do dpkg-deb -x "$f" sysroot/; done
export LD_LIBRARY_PATH=$PWD/sysroot/usr/lib/x86_64-linux-gnu
sysroot/usr/bin/ffmpeg -filters | grep drawtext
How it was checked
ffmpeg -filters lists drawtext; a test render with text, H.264 and AAC plays.

Scripting a password SSH login without sshpass or expect

Agent setup memory ·

Symptom
A script must log in to a password-only server. sshpass: command not found, no expect, no sudo to install them.
Cause
OpenSSH reads passwords from the terminal by default, so a script can't type one in.
Fix
OpenSSH can ask a helper program instead. Set SSH_ASKPASS to a small script that prints the password, SSH_ASKPASS_REQUIRE=force (OpenSSH 8.4+), and run ssh under setsid -w so it has no terminal. Keep the helper in a private temp folder and delete it after.
export SSH_ASKPASS=$TMP/askpass.sh SSH_ASKPASS_REQUIRE=force
setsid -w ssh user@host 'echo OK' < /dev/null
How it was checked
OK and the host name come back; scp works the same way.

Remotion audio fade silently doesn't render

Video pipeline memory ·

Symptom
A fade on a long audio Sequence never happened in the final video, with no error. Sometimes ffmpeg failed with "Missing ')' or too many args in 'if(between(t,...".
Cause
A function volume={(f) => ...} on a long Sequence is baked into one giant nested ffmpeg if(between(...)) expression, one branch per frame. Past a few hundred frames it overflows the expression parser.
Fix
Use flat volumes in Remotion and apply fades after rendering with ffmpeg afade. Short per-frame volume functions (a few frames) are fine.
How it was checked
ffmpeg -af astats on the export: the fade is visible in the levels and the peak stays under 0 dB.

LLM API suddenly returns model_not_found

Fact-check API memory ·

Symptom
Calls that worked before now fail with model_not_found for the default model.
Cause
The provider retired the model ID hard-coded as the default.
Fix
Pick a current model from the provider's live model list, keep the ID in config rather than code, and log the model name with every failure.
How it was checked
A startup check that compares the configured model against the provider's model list.

NotFittedError after reloading a saved TF-IDF index

Fact-check API memory ·

Symptom
Search worked until the service restarted; then query() raised NotFittedError ("not fitted").
Cause
Only the matrix was saved and loaded. The vectorizer that turns a query into a vector was never refit (or saved) on load.
Fix
On load, refit the vectorizer on the stored documents, or save it together with the matrix (for example with joblib).
How it was checked
A test that creates a fresh instance, loads from disk without adding anything, and runs a query.

Retry loop gives up on timeouts and 5xx errors

Fact-check API memory ·

Symptom
The code looked like it retried 3 times, but timeouts and server errors returned empty results immediately.
Cause
The loop only retried on HTTP 429. Every other failure fell through to return [].
Fix
Retry on timeouts, connection errors and 5xx as well, with backoff. Log the final failure instead of returning an empty list.
How it was checked
A test that makes the first call time out or return 503 and expects a retry.

Something fails, but there's no error anywhere

Fact-check API memory ·

Symptom
Fetches and parses returned nothing, and the logs were clean.
Cause
Seven bare except blocks caught the errors and did nothing with them.
Fix
Log inside every except block (at least the exception and what was being processed). Catch specific exceptions where you can.
How it was checked
grep for except blocks without a log call; a forced failure shows up in the log.

Live page calls localhost instead of the deployed API

Fact-check API memory ·

Symptom
The page worked on my machine; on the live site the requests never reached the backend.
Cause
The page's default API URL was http://localhost:8000.
Fix
Default to the deployed API URL and only use localhost when the page itself is served from localhost.
How it was checked
Open the live page with the browser's network tab and check which host the request goes to.

A page in a language subfolder loads without styles

Fact-check API memory ·

Symptom
The translated page under /tr/ showed plain unstyled HTML. The stylesheet request was a 404.
Cause
It used assets/style.css, which from inside tr/ points to tr/assets/style.css.
Fix
Use ../assets/ (or root-relative /assets/) for pages inside subfolders.
How it was checked
An internal link and asset scan after adding pages to a subfolder.

Visitors still see the old site after a deploy

Static site memory ·

Symptom
Changes were live on the server, but returning visitors kept getting the old pages.
Cause
The service worker served its cache. Its cache name hadn't changed, so it never refreshed.
Fix
Bump the cache name in sw.js on every deploy that changes files. Read the live value first: once the server was already ahead of the local copy.
How it was checked
curl the live sw.js after deploy and compare the cache name.

Upload reports success, but the file isn't where it should be

Static site memory ·

Symptom
scp exited with 0 both times, yet the live URL still served the old file or a 404.
Cause
The deploy command put the file in the wrong folder. Exit code 0 only means the copy happened somewhere.
Fix
Deploy with rsync --files-from from the repository root so paths are kept, then check every changed URL on the live site.
How it was checked
A curl diff of each changed live file against the local one.

Pages in the sitemap stay "Crawled, currently not indexed"

Static site memory ·

Symptom
Search Console kept these pages as crawled but not indexed for weeks.
Cause
They were listed only in the sitemap. No other page linked to them with a normal link (some were reachable only through JavaScript).
Fix
Give every page at least one static <a href> link from a related page. Add an orphan check to the site scan.
How it was checked
A crawl from the home page reaches every URL in the sitemap.

hreflang points to a page that doesn't exist

Static site memory ·

Symptom
Pages declared an English and x-default alternate, but those URLs returned 404.
Cause
The tags were added for an English version that had not been built.
Fix
Only declare hreflang for pages that exist, in both directions. Have the site scan request every hreflang target.
How it was checked
Every hreflang URL returns 200 and links back.

On-screen game buttons don't respond on phones

Static site memory ·

Symptom
Arrow buttons worked with a mouse, but tapping them on a phone did nothing useful.
Cause
They only had onclick handlers.
Fix
Add touchstart handlers with preventDefault, registered with { passive: false }.
How it was checked
A real emulated tap (for example Playwright with a phone profile and .tap()), not a grep for handlers.

Arrow keys move the game and scroll the page

Static site memory ·

Symptom
Pressing arrow keys moved the player and scrolled the whole page at the same time.
Cause
The keydown handler used the keys without stopping the browser's default action.
Fix
Call e.preventDefault() for the keys the game handles (arrows, space).
How it was checked
Play on a page that is taller than the window.

Clock hands drawn 90° off on canvas

Static site memory ·

Symptom
Every time showed rotated by a quarter turn: 6:00 drew the hour hand at 3 and the minute hand at 9. All tests passed.
Cause
The −90° (12 o'clock) offset was applied twice: the hand shape already pointed up, and π/2 was still subtracted from the angle. The tests read the answer value, not the pixels.
Fix
Apply the offset exactly once. Check drawings with known inputs (3:00, 6:30, 9:15, 12:45) on a screenshot.
How it was checked
Screenshots of the known times before shipping any game where the answer is drawn.

SVG <use> elements paint as giant blobs

This site's memory ·

Symptom
An animation with 100 small nodes passed all of the agent's checks, but in a real browser the nodes covered the page as huge colour blobs.
Cause
Each node was <use href="#symbol"> without width and height, so the symbol rendered at 100% of the parent viewport.
Fix
Use sized primitives (circle, rect, path), or give every <use> an explicit width and height.
How it was checked
A real browser screenshot of every SVG before it ships. An agent without a browser can't see this.

SyntaxError in a Python f-string that holds CSS or JS

This site's memory ·

Symptom
The build script failed with "f-string: expecting '}'" after inline JavaScript was added to the page template.
Cause
The template is one big f-string. The { } of the JavaScript and CSS were read as Python placeholders.
Fix
Double every literal brace inside the f-string ({{ and }}), or move the script into a separate file.
How it was checked
The build runs and the generated page contains single braces.

Old backup copies were publicly reachable

This site's memory ·

Symptom
An old version of the home page could still be opened through its .bak file name.
Cause
Backups were made next to the live files, inside the web root, and the server serves any file there.
Fix
Keep backups outside the web root and delete stray .bak and old copies from it.
How it was checked
A listing of the web root after every deploy shows no .bak or old copies.

Where these come from

Salvor is a repo-native memory layer for coding agents. When an agent hits a failure that is worth remembering, it proposes a learned-failure record, and it is saved only after a human says yes.

The next agent session reads those records first, so the same mistake is caught before it repeats. This page is a cleaned, public copy of some of those records.

Disclosure: I contribute to Salvor. The records are mine; the tool is Dan's open-source project.

How I tested Salvor → · Salvor on GitHub ↗

Found a mistake, or a fix that worked differently for you? Write to me. Contact →