5.5 KiB
type, title, description, tags, timestamp
| type | title | description | tags | timestamp | ||||
|---|---|---|---|---|---|---|---|---|
| guide | Daily Run Guide | Operator- and tester-focused walkthrough of one daily cron tick — repository synchronization, anniversary matching, deduplication, logging, and retry behaviour. |
|
2026-08-04T17:51:00Z |
Purpose
A tester or operator needs to know what should happen each time the cron tick fires. This guide describes the contract from the outside of the container, in observable terms: log lines, exit codes, files appearing on the volume, and the "silent on no matches" behaviour that the runner enforces.
Triggering a Run Manually
docker compose exec tenbackward /usr/local/bin/python -m tenbackward
This is the same command the container's cron entry issues once per
day at RUN_AT. Use it to verify behaviour without waiting for the
scheduled tick.
What the Run Does
Before scanning, the process ensures the configured blog repository exists
under /app/data/blog and is fast-forwarded from BLOG_REPO_URL. It then
walks _posts/blog/**/*.md, finds posts whose date is exactly ten years
before today, skips IDs already present in posted.json, composes a
single German-language Mastodon status (default prefix
Heute vor 10 Jahren:) listing each new match's title and canonical URL
followed by the configured hashtags, and — only after the Mastodon API
call succeeds — records the relative paths of the published posts in
posted.json.
What You Should See
The bot uses a JSON-per-line logger that writes to
/app/data/cron.log (because the cron line redirects with
>> /app/data/cron.log 2>&1). Inspecting the file with
docker compose exec tenbackward tail -n 100 /app/data/cron.log
should show output similar to:
{"ts": "2026-08-04T09:00:00+00:00", "level": "INFO", "logger": "tenbackward", "message": "startup", "event": "startup", "version": "0.1.0", "site_url": "https://blog.example.com", "run_at": "09:00", "tz": "Europe/Berlin", "hashtags": "#throwback,#10backward", "throwback_prefix": "Heute vor 10 Jahren:"}
On the current scaffold (_iter_candidates is intentionally empty),
this is the only line you should see — the runner emits no
run_complete when no candidates were found. This is the
"silent on no matches" contract.
Expected Container Behaviour
| Scenario | Lines in cron.log |
Container exit |
|---|---|---|
| Config valid, no anniversary matches | 1× startup |
0 |
| Blog clone or pull fails | 1× blog_repo_error |
1 |
| Config valid, matching IDs are new | 1× startup, then 1× run_complete with posted=N |
0 |
| Config valid, all matches already posted | 1× startup, then 1× run_complete with posted=0 |
0 |
| Pipeline raises once, recovers | 1× pipeline_error, then 1× run_complete |
0 |
| Pipeline keeps raising (budget exhausted) | MAX_RETRIES + 1 × pipeline_error, then 1× pipeline_failed |
1 |
| Config invalid | 1× configuration_error (extras describe what failed) |
2 |
Tester tip —
docker compose psshould report the container asrunningafter apipeline_failedexit only if cron has not yet fired again. A single failed pipeline tick does not kill the container itself; cron re-runs it the next day.
Files the Tester Should Look For
| Path (inside container) | When it appears |
|---|---|
/app/data/cron.log |
Always (cron appends stdout/stderr here). |
/app/data/posted.json |
Created when the matcher or a state check first loads the store; rewritten only when a new ID is posted. The file contains relative Jekyll paths under a posted list. |
To verify these from the host:
docker compose exec tenbackward ls -la /app/data
docker compose exec tenbackward cat /app/data/cron.log
Visual Elements
This is a server-side bot, so there is no UI. The "UI" consists of:
- Log lines on stdout — one JSON object per
INFO/ERRORevent. - Exit code —
0(healthy),1(pipeline exhausted), or2(config). posted.json— JSON state file; its mtime changes whenever a new id is persisted./app/data/blog— synchronized local clone containing the Jekyll posts scanned for anniversaries.
Examples
Healthy scaffold tick
$ docker compose exec tenbackward python -m tenbackward
{"ts":"...","event":"startup",...}
$ echo $?
0
Bad RUN_AT
$ RUN_AT=25:99 docker compose exec tenbackward python -m tenbackward
ERROR: RUN_AT='25:99' must be in HH:MM (24-hour) format
$ docker compose ps
tenbackward Exit 1
(The error message lands on stderr in the entrypoint path and is
captured by cron.log.)
Missing required env var
Unset MAX_RETRIES, restart the container:
$ docker compose up --build
tenbackward | ERROR: missing required environment variable(s): MAX_RETRIES
tenbackward exited with code 1