--- type: guide title: Daily Run Guide description: Operator- and tester-focused walkthrough of one daily cron tick — startup, pipeline pass, silent-on-no-matches, and retry behaviour. tags: [guide, run, daily, tester] timestamp: 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 ```bash 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 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: ```json {"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": "Throwback:"} ``` 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, zero candidates (scaffold) | 1× `startup` | `0` | | Config valid, future blog step posts N≥1 | 1× `startup`, then 1× `run_complete` with `posted=N` | `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 ps` should report the container as > `running` after a `pipeline_failed` exit *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 on first persist; only rewritten when a new id is posted. The scaffold run does **not** create this file. | To verify these from the host: ```bash 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`/`ERROR` event. * **Exit code** — `0` (healthy), `1` (pipeline exhausted), or `2` (config). * **`posted.json`** — JSON state file; its mtime changes whenever a new id is persisted. # Examples ## Healthy scaffold tick ```bash $ docker compose exec tenbackward python -m tenbackward {"ts":"...","event":"startup",...} $ echo $? 0 ``` ## Bad `RUN_AT` ```bash $ 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: ```bash $ docker compose up --build tenbackward | ERROR: missing required environment variable(s): MAX_RETRIES tenbackward exited with code 1 ``` # Related * [Environment Variable Setup](/operations/environment-setup.md) * [Pipeline Runner](/architecture/pipeline-runner.md) * [Logging & Run Summary](/architecture/logging.md)