This runbook is for the repository owner. It assumes no code editing.
The bot collects Jeonghan-related source updates, organizes them, prepares Persian ChannelStyle captions/translations and sends them to a private Telegram review chat. It also supports recent replay, source-window retrieval, archive search, inbox/reminders, media delivery and a nightly fic digest.
It does not automatically publish to a public Telegram channel.
File: .github/workflows/main.yml
- validation on relevant push/PR changes;
- scheduled runtime: تقریباً هر پنج دقیقه، با quiet window کوتاه اطراف اجرای nightly؛
- manual dispatch has
checkandlivemodes.
File: .github/workflows/fic-digest.yml
- scheduled at
30 18 * * *UTC; - runtime shares the private SQLite store and runtime concurrency group with main.
File: .github/workflows/translation-benchmark.yml
- separate from fast runtime validation;
- checkpoint/resume is intentional;
- a 429 is an external quota result, not a reason to edit benchmark output manually.
GitHub schedules use the default branch and can be delayed: https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule.
Open the repository → Settings → Secrets and variables → Actions → New repository secret.
Required for real Telegram/X runtime:
TELEGRAM_BOT_TOKENTELEGRAM_ADMIN_USER_IDTELEGRAM_REVIEW_CHAT_IDX_COOKIE
Optional:
GEMINI_API_KEY— ChannelStyle/model features; fallback exists if absent.STATE_BACKUP_KEY— enables encrypted recovery backup.SENTRY_DSN— optional scrubbed technical observability.
GEMINI_MODEL can be supplied as an environment/Actions variable where the workflow supports it; code default is gemini-3.5-flash-lite.
Never paste real tokens, cookies, chat/user IDs, backup keys, private messages or private SQLite content into GitHub issues, PR comments, public logs or tracked files.
Run locally on a trusted machine:
python -c "import base64,secrets; print(base64.b64encode(secrets.token_bytes(32)).decode())"Copy the one-line result directly into a GitHub Actions secret named exactly:
STATE_BACKUP_KEY
Do not add quotes, spaces or a trailing comment. Do not save it in the repository.
The decoded key must be exactly 32 bytes. tools/state_backup.py rejects malformed base64 or any other decoded length.
Normal bot validation/runtime is not broken by absence of this optional secret. The workflow derives a stable process key from TELEGRAM_BOT_TOKEN, masks it before exporting it to later steps, and can still create authenticated encrypted recovery snapshots. A dedicated STATE_BACKUP_KEY is preferable because rotating the Telegram token also rotates the derived key.
Do not rotate the Telegram token and discard the old token before confirming that current state is healthy or a dedicated recovery key is active.
The workflows cache:
.state/state.json.state/private-review.sqlite3
Cache is an optimization and continuity mechanism. GitHub can evict caches. GitHub documentation: https://docs.github.com/en/actions/reference/workflows-and-actions/dependency-caching.
When a recovery key is available (dedicated or derived):
- cache restore happens first;
- if required state is missing, workflow lists non-expired
private-state-backupartifacts newest→oldest; - each artifact is downloaded to
/tmp, unpacked and authenticated/validated withpython -m tools.state_backup validate; - the first valid candidate is restored;
- JSON and SQLite are validated before replacement;
- after runtime/checkpoint, a new ciphertext-only backup can be uploaded with the workflow's current three-day retention; normal runs refresh snapshots on a bounded cadence.
The uploaded artifact contains .state/private-state-backup.enc, not plaintext JSON or SQLite.
Because this repository is public, encryption is essential. GitHub documents artifact read access here: https://docs.github.com/en/rest/actions/artifacts.
Do this after the branch has been human-approved/merged. A dedicated STATE_BACKUP_KEY is recommended but the production workflow can use its masked derived key.
- Open Actions.
- Open a completed scheduled/live run of
Jeonghan Daily Review BotorNightly Jeonghan Fanfic Digest. - Confirm
Create authenticated encrypted recovery backupsucceeded. - Confirm
Upload encrypted recovery backupsucceeded. - At the run bottom, verify an artifact named
private-state-backupexists. - Do not post/download/decrypt it on an untrusted computer.
- Confirm workflow logs contain no message bodies, cookie/token values or SQLite plaintext.
This is production verification, not unit-test evidence.
Do not intentionally delete production state just to test recovery.
A safe owner verification is:
- confirm at least two successful encrypted backup artifacts exist;
- use a disposable local directory or temporary test branch/workflow only if separately authorized;
- provide the same
STATE_BACKUP_KEYthrough the environment, never command-line history if your shell records it; - run
python -m tools.state_backup validate --input <encrypted-file>; - restore into an empty disposable directory;
- verify
state.jsonparses and SQLite returnsPRAGMA quick_check = ok; - delete the disposable plaintext copy securely according to your machine policy.
The normal workflow itself never prints decrypted data.
Key rotation makes old artifacts unreadable with the new key.
Safe process:
- Do not delete the old key immediately if recovery continuity matters.
- Confirm current production state is healthy and cached/local state exists.
- Generate a fresh 32-byte base64 key.
- Replace the GitHub secret
STATE_BACKUP_KEYwith the new value. - Allow a successful runtime to create a new backup using the new key.
- Verify the new artifact exists and its workflow succeeded.
- Old artifacts encrypted with the old key will fail authentication and the workflow will try older/newer candidates; once only new-key backups are needed, remove obsolete old artifacts if desired.
If you lose the old key before creating a new-key backup, old ciphertext cannot be recovered by repository code.
When X authentication expires or becomes invalid:
- obtain a fresh cookie through your normal authorized account/browser process;
- replace only the GitHub Actions secret
X_COOKIE; - do not commit a cookie file/database;
- run/observe validation; live X access must be tested in an authorized runtime, not by exposing the cookie;
- if logs show configuration parsing failure, check that required
auth_tokenandct0fields exist in the supported format.
Do not paste X cookies into issues or chat screenshots.
Without revealing values:
TELEGRAM_BOT_TOKENmust be present;TELEGRAM_ADMIN_USER_IDmust parse as an integer > 0;TELEGRAM_REVIEW_CHAT_IDmust be a non-zero integer;- bot output must arrive only in the intended private review chat;
- unauthorized users/chats must not gain review actions.
After merge, perform a small smoke check with /start, /menu, /status and one non-destructive archive/recent command.
A Telegram flood-control response is HTTP/API 429 with retry_after. The transport waits according to the bounded retry policy and treats it as transient. Do not repeatedly restart workflows to fight a Telegram 429.
Bot API reference: https://core.telegram.org/bots/api#responseparameters.
The benchmark/runtime may report 429 RESOURCE_EXHAUSTED. Google documents request/token/day/spend-related rate limits: https://ai.google.dev/gemini-api/docs/rate-limits.
Do not change the approved model merely to make the audit green and do not handwrite benchmark outputs.
When quota becomes available:
- open
Channel Style Translation Benchmarkin Actions; - allow the normal PR/workflow trigger or use manual dispatch only when owner-authorized;
- keep the existing checkpoint/resume behavior;
- verify completed cases are restored rather than regenerated;
- a complete machine result still requires human SOURCE→OLD→NEW review before Part 4 can pass.
Expected blocked state while quota is unavailable:
- Benchmark harness: VERIFIED WORKING
- Live Gemini generation: BLOCKED BY EXTERNAL QUOTA
- Human quality gate: NOT PASSED
- Merge authorization: NOT GRANTED
The benchmark workflow treats only exit code 3 backed by an INCOMPLETE
checkpoint containing explicit 429/RESOURCE_EXHAUSTED evidence as
BLOCKED_BY_EXTERNAL_QUOTA. It uploads that incomplete artifact and writes the
warning to the job summary. Exit 3 without that evidence, malformed checkpoints,
verifier defects, and all other non-zero exits remain genuine workflow failures.
- Compile failure: syntax/import defect; do not run live.
app --checkfailure: tracked config/corpus validation defect; inspect exact message.- Unit-test failure: repository gate failed; fix before merge.
pip checkfailure: installed dependency incompatibility; inspect manually before changing versions.- Encrypted backup validation failure: workflow tries older artifacts. If artifacts exist but none validate, runtime fails closed.
- No recovery artifact exists: first-run/cache behavior is allowed; create a backup after a healthy runtime if key is configured.
- Telegram transient failure: bounded retry; do not delete state/offset manually.
- Gemini 429 in benchmark: external quota; checkpoint should survive.
If private-review.sqlite3 fails PRAGMA quick_check:
- stop destructive/manual edits;
- preserve the corrupt file privately if forensic analysis is needed;
- prefer a previously authenticated encrypted backup;
- validate candidate before restore;
- do not replace a healthy state file with an unvalidated copy;
- if no valid recovery exists, use the repository's rebuild/first-run behavior only with explicit awareness of what historical state may be lost.
If state.json is malformed, the backup tool rejects it rather than encrypting/restoring it as valid state.
Before merge, rollback means moving/closing PR work — never force-push/reset production main as part of normal operation.
After merge, if a severe regression occurs:
- identify the exact production commit used by the failed scheduled run;
- preserve logs/artifacts privately;
- revert through a normal reviewed commit/PR rather than rewriting main history;
- keep state/recovery backups untouched until the failure is understood;
- never restore plaintext private state into a public branch.
Repository CI cannot prove scheduled default-branch external behavior before merge. After human merge approval and merge:
- wait for the next scheduled main run; do not trigger live solely for audit unless authorized;
- open the workflow run;
- in Checkout/log metadata, record the commit SHA actually checked out;
- verify it descends from/equals the merged commit expected on
main; - confirm validation succeeded before live steps;
- confirm at least one private bot pass completed successfully;
- confirm no public channel output occurred;
- confirm encrypted backup steps if key is configured;
- verify nightly fic runtime similarly on its next schedule;
- only then upgrade those matrix rows from implemented/CI-verified to production-verified.
GitHub scheduled workflows run from the default branch. To confirm the exact commit:
- open Actions → workflow → scheduled run;
- inspect the run's commit/Checkout step;
- copy the SHA shown by
git log -1 --format=%H/checkout metadata if present; - compare it with repository
mainhistory at that time; - remember a scheduled workflow can be delayed and therefore may not start at the exact cron minute.
Never infer the production SHA from the time alone.