Troubleshooting

Running it — part 6 of 6

SymptomCause and fix
Anything at all, and you need to know what you're running./blog.sh version -- it needs neither env.sh nor a config, on purpose.
The site looks wrong, or you want to change how it looks./style.sh -- a menu over everything that decides how the site looks and what it says about itself; it lists its own sections. The bio, the footer note, the copyright line and the banner's claim are Markdown, the same as a post, and raw HTML still works in them; a multi-paragraph value needs YAML's literal block (`-) -- the notes in config/site.yml.example` show why. Picking a palette offers a preview (light and dark side by side, uploaded to the site too so a phone can see it) before anything is written -- temporary on purpose, the next build takes it down -- and the run ends by offering a rebuild.
Anything config-shaped, and you want the whole picture./blog.sh doctor -- it reads whatever is on disk and reports every problem at once, each with a fix line. It runs on a config too broken for anything else to load, including one whose YAML won't parse, and needs neither env.sh nor a valid config. Add --online to also ask whether the feeds, the analytics script and the access token still answer, and whether the deploy target does -- with what stands in its root that the site did not put there, and --strip-location to clean the location out of photos saved before the engine did it on the way in (Writing from a phone); rebuild afterwards, since the site is built from what it cleans.
Anything archive-shaped -- a hole where a picture should be, a link that leads nowhere./blog.sh check -- it walks every post and every media file and says what is broken inside the archive, each finding with a fix line; --online additionally asks whether the links that leave the site still answer (Checking the archive). It only ever reports: nothing is deleted or rewritten, so it is safe to run at any time, including from cron.
config/site.yml is not valid YAMLThe message names the line and column. Almost always a tab where spaces belong, a missing quote, or a colon inside an unquoted value (title: Colon: here). ./blog.sh doctor says the same thing without stopping at the first problem.
A save aborted and took your text with itIt didn't: the text is in .last-edit.md, and the next add/edit offers it back -- [r] opens the editor on it, [d] throws it away, [c] leaves it alone. Text from an interrupted edit <slug> is only offered to that same post: restoring it into an add would make a second post out of it, so the offer names the command that does continue it.
Missing env.sh./setup.sh, or copy the template by hand: cp env.sh.example env.sh && chmod 600 env.sh. An unedited copy works locally.
Missing config/site.ymlSame two ways: ./setup.sh, or cp config/site.yml.example config/site.yml and fill it in -- the build refuses to guess.
Duplicate year/slug ... build stoppedTwo posts resolve to the same URL and media directory. Rename one slug; the build aborts rather than silently overwriting one with the other.
Deploy stopped with a "% drop/increase" messageOne of the four guards (Deploying) -- broken build until proven otherwise, --force only when the change is intended.
Deploy or save stopped naming an oversized fileThe one file-size limit (Deploying) -- shrink the file, or take it out of the post and link to it instead.
N deploys in a row have not finishedSomething is refused every time; the failures listed under that line say which (Deploying).
Surfer ... refused the access token (HTTP 401)Surfer 7 no longer takes access tokens. Create an app password in your Cloudron profile (Profile -> App Passwords) and set SURFER_USERNAME and SURFER_PASSWORD in env.sh (Getting started).
Surfer ... refused the username and password (HTTP 401)SURFER_USERNAME is the Cloudron username, not the e-mail, and SURFER_PASSWORD an app password, not the Cloudron sign-in password. A refused sign-in stops the deploy at the first file, and ./scripts/deploy-web.sh then exits with 4 rather than 1, so a script can tell "fix env.sh" from "try again".
Mastodon API returned 401 / toot was not createdMASTODON_ACCESS_TOKEN missing, expired, or lacking the write:statuses scope. The post itself is fine -- fix the token and use ./blog.sh toot <slug>.
Posting to Bluesky failed / announcement not sentBLUESKY_APP_PASSWORD missing, revoked, or it's the account password instead of an app password (Settings → Privacy and security → App Passwords). The post itself is fine -- fix it and use ./blog.sh bluesky <slug>.
Every comment disappeared after turning on comments.approvalExpected until you star them -- moderation publishes only favourited replies (Cron). If starring changes nothing either, ./blog.sh doctor --online catches the usual cause: a token without read:statuses.
A comment you starred still isn't on the siteIt arrives with the next refresh-sidebar.sh run; an older post waits for the full pass, which --full does now (Cron). Check the cron job is actually installed.
comments.approval is on but MASTODON_ACCESS_TOKEN is not setThe cron run refused to publish rather than emptying every thread; the pages keep their last known comments until the token is there (Cron). Same for BLUESKY_APP_PASSWORD on a Bluesky site.
Sidebar widget disappeared from the pageIts fetch returned nothing repeatedly (refresh-sidebar logs say which) -- the widget card hides when its JSON is empty/unreachable. Check the instance/feed URL in config/site.yml.
MISSING media: <slug> -> <file> during buildA post references a file that isn't in media.nosync/<year>/<slug>/ -- restore the file or edit the post. The build continues, and a copy already uploaded stays on the site rather than being pruned, so the page keeps working until you fix it.
Unreadable post file(s) ... build stoppedA post's JSON is truncated or isn't a post object -- the message names every offending file. Fix or remove them; list and the pickers keep working meanwhile and name it too.
The image size could not be read when attaching a photoPNG, JPEG, GIF and WebP are measured; anything else is attached and rendered without reserved space, so the page jumps once while loading.
HEIC displays only in Safari when attaching a photoThe iPhone default format. Convert it with the command the message prints, set media.convert_heic: true to have the engine do it, or set the phone to Settings → Camera → Formats → Most Compatible.
A post sent from the phone came back refusedThe answer names a code. bad_name, too_large, truncated, empty_input, empty_file and bad_base64 are about the delivery -- send it again, and see A post sent from the phone itself for the ceiling and the closing dot. bad_reference means the markdown named a picture by a path rather than a bare filename. bad_markdown is markdown no blocks can be made of -- a picture inside a sentence, a video with no caption -- and the message says where; unknown_frontmatter_key names a header line the engine does not know, not_text a file that is not UTF-8 text, empty one with nothing under its header. A file is read as the post when its name ends in .md (or .md.txt); under any other name, .markdown included, it is kept as an attachment. missing_images means the text arrived before a picture it names: send it again, the pictures already there are found.
name_taken from the receiverSomething in incoming/ under that name is not a plain file -- a directory, or a symlink. Nothing was replaced; clear the name on the server.
busy from the receiverAnother delivery was still being taken in when this one had waited as long as it waits (BLOGSH_TURN_SECONDS, 300). Nothing of this one was stored; send it again.
write_failed, no_incoming, no_engine, no_tmp, no_cdThe installation, not the delivery: the path in the shortcut's command is wrong, incoming/ is missing, or the account behind the key cannot write into it. Every one of them answers with its code and leaves with a zero status, the same as any other refusal: the phone discards the output of a command that failed, and these are the answers a first delivery to a new install most needs to read.
/markdown/ page missingtemplates/markdown-cheat-sheet.<lang>.md was removed -- restore it from the repo (git checkout templates/).
A published post shows the wrong datePublishing uses "now" and scheduling uses the date you entered, so a surprising date means a date: line was typed into the frontmatter by hand -- it's respected, including past dates (which skip the homepage -- by design).
sftp deploy hangsIt's waiting for a password -- the sftp backend needs key-based auth (see Getting started).

When in doubt: ruby build/build_blog.rb and ./scripts/deploy-web.sh --dry-run are both safe to run any time -- the build only writes into public.nosync/, and a dry run touches nothing at all.