Both scheduled tasks push their own output. The briefing task publishes each briefing; article-spotter publishes each article draft. Netlify builds on the push, so a run that finishes at 7:12am is live by about 7:13.
The push happens from the cloud session, straight to GitHub. It does not go through the
device bridge, because the bridge blocks git writes in a mounted folder (EPERM: unlink .git/index.lock). That also means publishing keeps working when the Mac Mini is asleep,
off, or has no folder connected.
GitHub → Settings → Developer settings → Personal access tokens → Fine-grained tokens → Generate new token.
| Field | Value |
|---|---|
| Token name | fervorai-site-publish |
| Expiration | 90 days (calendar reminder to rotate) |
| Repository access | Only select repositories → Ktopper/fervorai-site |
| Permissions | Repository permissions → Contents: Read and write. Nothing else. |
That token can commit to one repo and do nothing else. It cannot read your other repos, cannot touch settings, cannot delete anything. If it leaks, revoking it costs you one click and a re-generate.
mkdir -p ~/Documents/Claude/Projects/"Meduim Writer GOAT"/.secrets
pbpaste > ~/Documents/Claude/Projects/"Meduim Writer GOAT"/.secrets/fervorai-github-token
chmod 600 ~/Documents/Claude/Projects/"Meduim Writer GOAT"/.secrets/fervorai-github-token
It lives in the workspace folder, not in this repo, so it can never be committed by accident.
The file holds the token and nothing else, no export, no quotes, trailing newline is fine.
cd ~/dev/fervorai-site
git add scripts/publish.sh PUBLISHING.md && git commit -m "add auto-publish script" && git push
This has to be pushed before the first automated run, because each run clones the repo and executes the copy of the script it finds inside.
Netlify → Site configuration → Build & deploy → Continuous deployment: branch main,
auto-publish on. Nothing else to change.
One bootstrap block, added as the last step of each task. It finds the token, clones, and
hands off to scripts/publish.sh inside the clone:
TOKEN_FILE="$(ls /sessions/*/mnt/"Meduim Writer GOAT"/.secrets/fervorai-github-token \
/mnt/user-data/uploads/"Meduim Writer GOAT"/.secrets/fervorai-github-token \
2>/dev/null | head -1)"
if [ -z "$TOKEN_FILE" ]; then
echo "PUBLISH SKIPPED: token file not reachable this run"
else
GH_TOKEN="$(tr -d '\r\n' < "$TOKEN_FILE")"
rm -rf /tmp/fervorai-pub
git clone --quiet --depth 1 \
"https://x-access-token:${GH_TOKEN}@github.com/Ktopper/fervorai-site.git" /tmp/fervorai-pub \
&& GH_TOKEN="$GH_TOKEN" bash /tmp/fervorai-pub/scripts/publish.sh <FILES>
fi
<FILES> is the absolute path of the briefing (or the two article drafts) the run just wrote.
Pass the *-trending-ai.md briefing and the article .md files only.
X-articles, X-bursts, newsletters, promo posts, selection memos, the topic ledger, and image-prompt files are rejected by filename even if you pass them. Two reasons: the X-article is a near-duplicate of the briefing and would compete with it in search, and the rest are working files, not published pages.
Every failure path exits 0 and prints one line, so a publish problem never fails a briefing run:
| Line | Meaning |
|---|---|
PUBLISHED: 1 briefing, 2 articles pushed to … |
live, Netlify building, page URLs follow |
PUBLISH: nothing new … |
the files were already in the repo, no commit made |
PUBLISH SKIPPED: GH_TOKEN is empty … |
token file missing or the folder wasn't mounted |
PUBLISH FAILED: clone failed … |
token expired or revoked, or no network |
PUBLISH FAILED: push rejected … |
rebase-and-retry also failed, needs a look |
The token is never printed, and it's scrubbed from any error text before it's shown. It's also
stripped out of the clone's .git/config right after cloning, so it isn't left sitting in a
temp dir.
A skipped run loses nothing. The next run publishes any file the repo doesn't have yet, or you can catch up by hand:
cd ~/dev/fervorai-site
cp ~/Documents/Claude/Projects/"Meduim Writer GOAT"/briefings/2026-07-27-*-trending-ai.md content/briefings/
git add content && git commit -m "publish: catch-up" && git push
Generate a new one, overwrite the file in .secrets/, revoke the old one on GitHub. Nothing in
the repo or the task prompts refers to the token's value, so there's nothing else to update.