Troubleshooting

Start with doctor

rollup doctor
rollup doctor --json
rollup doctor --full
rollup doctor --network   # or pass --ollama to enable Ollama checks

Fix hints are always included. Exit code 0 means no error-level checks failed (warnings are OK).

Common issues

Newsletter root does not exist

Point --root at your Thunderbird .sbd tree, or use the synthetic fixtures:

rollup inventory --root tests/fixtures/Newsletters.sbd

Writable path inside mail root

Move --output-dir, --state-dir, and --log-dir outside --mail-root.

Another digest run is in progress

A lock is held at state/rollup.lock. Wait for the other run, or if the process is dead, the next run recovers stale locks automatically.

Empty digest / everything skipped outside window

Fixture or live mail dates may be outside --lookback-days. Increase the lookback or regenerate fixtures with python tests/generate_fixtures.py.

Ollama not reachable

Default digests do not need Ollama. If you passed --ollama, start the local server or omit the flag to use preview summaries.

Provider errors vs programming faults

Rollup degrades only named provider transport/payload failures, such as requests.RequestException, malformed provider JSON, or Unicode decode errors from provider payloads. Programming faults such as TypeError or AttributeError are not converted into fallbacks; they hard-fail so the bug is visible.

Parse anomalies vs fatal errors

Kind

Meaning

Fatal parse error

Message produced no ParsedMessage (corrupt entry, open failure)

Parse anomaly

Recoverable (encoding replaced, body truncated, invalid date)

Filter outcome

Outside lookback window / seen undated

Content quality

Empty body with subject — still a valid short notification

Missing dates use the established undated-message path; they are not fatal parse errors.

Manifests

Each non-dry-run digest writes state/manifests/<timestamp>-<run_id>.json (schema version 2). Readers still accept schema 1. Failure manifests are written whenever state_dir is writable. Inspect:

cat state/manifests/latest.json
rollup cron status

Manifests are local operational records (paths and folder names may be sensitive on shared machines). They never store message bodies, subjects, Message-IDs, prompts, model responses, or patch text.

Structured logs follow the same privacy rule: they may record status, counters, codes, and bounded exception messages, but not newsletter subject/body text, prompts, model responses, or patch contents.

Schema v2 may include final_review (apply skip reason, reject counts by code, auto-edited prose flag) and group_summaries (ollama_calls, cache hits, stream / cache errors, degraded).

Apply mode skipped every patch

Check the manifest final_review.apply_global_skip_reason and logs. Common codes: fingerprint_missing, fingerprint_mismatch, unsafe_to_publish, unattended_patch_cap, unattended_char_cap. Cron apply also requires --final-review-allow-cron-apply and conservative policy.

Final-review apply binds patches to the digest fingerprint. Live model responses may echo either echoed_digest_fingerprint or the schema alias digest_fingerprint; cached results preserve only the model/cache echo and never synthesise a missing echo from the host-computed fingerprint. Missing or mismatched echoes skip the whole patch set.

Group summaries degraded / exit 2

Partial exit with a usable digest usually means stream failures, cache read/write errors, or all attempted group blurbs failed. Member entry summaries still render; deterministic group headers remain when a blurb is omitted.

Grouping looks wrong

rollup digest --grouping-report
rollup digest --no-grouping

v1 groups notification_stream, daily_editions, and sender_batch (including an auto fallback to same-source batches). Essays and long-form messages always stay standalone.

LinkedIn fetch failed (401 / 429 / checkpoint)

fromMember searches use Voyager (profileUpdatesV2) with your LinkedIn session. Set both cookies before --linkedin or [linkedin].enabled = true. Never store them in TOML. See CONFIG.md.

export ROLLUP_LINKEDIN_LI_AT='…'            # DevTools → Cookies → li_at
export ROLLUP_LINKEDIN_JSESSIONID='ajax:…'  # same pane; JSESSIONID (quotes optional)
rollup digest --linkedin
  • Missing ROLLUP_LINKEDIN_LI_AT: fetch refuses to start. Dry-run warns instead of calling the network.

  • Missing ROLLUP_LINKEDIN_JSESSIONID: fromMember searches need it as Voyager CSRF (csrf-token). Copy it from the same cookie pane as li_at.

  • 401 / session expired: refresh both cookies from a logged-in browser. They rotate together; a new li_at with a stale JSESSIONID still 401s.

  • 0 posts / all undated (older builds): current Rollup dates posts from activity ids and applies lookback after fetch. If Messages parsed is 0, the session may be valid HTML-only (no Voyager) — confirm the log line Fetching LinkedIn fromMember feed (N authors) via Voyager and that the URL has fromMember=.

  • 429: rate limited — wait and retry. Mail-only digests may still publish as partial (exit 2).

  • Checkpoint / authwall: complete LinkedIn’s verification in the browser, then export fresh cookies.

  • Listing cache still showing old posts: LinkedIn listings and article bodies persist in rollup.db for [linkedin].fetch_ttl_hours (default 24). Pass --linkedin-refresh, or set fetch_ttl_hours = 0, to force a live pull. A fetch failure may reuse a stale snapshot (linkedin_cache_stale).

  • LinkedIn-only run (--folder linkedin:… and no mbox): fetch failure is exit 1, not partial.

  • Dry-run / Settings GET: never contacts LinkedIn.

  • Link-post still a short teaser: article fetch is on by default. Confirm [linkedin].article_fetch is not false and you did not pass --no-linkedin-article-fetch. Voyager must expose ArticleComponent (content.navigationContext.actionTarget). Job posts and posts without a link card stay commentary-only. A failed article GET leaves the teaser and sets a parse warning (linkedin_article_fetch_failed, linkedin_article_empty, linkedin_article_url_invalid); it does not fail the digest.

  • How to copy cookies: EXAMPLES.md.

Reddit fetch failed (429 / missing sub)

Rollup fetches public subreddit listings via a transport ladder: OAuth JSON (when env credentials are set) → public JSON → www RSS → old.reddit RSS. Unauthenticated feeds are often rate-limited (~1 request per minute per IP).

rollup digest --reddit --lookback-days 7
  • 429 / empty body: rate limited or bot wall — wait and retry. Mail-only digests may still publish as partial (exit 2). Rollup retries 429s once per sub across the ladder, then retries failed subs once more after a longer backoff. The CLI prints a planned wait (70s between subs) at fetch start and remaining time per sub; HTTP 429s add extra wait beyond that estimate.

  • 404: subreddit does not exist or name is wrong — not retried. Check /reddit or [reddit.subs.*] in TOML.

  • Subs whose names start with r: older builds mangled names like raspberry_pi → aspberry_pi (bad lstrip("r/")). Upgrade fixes this; if TOML has [reddit.subs.aspberry_pi], rename to raspberry_pi.

  • Stale listing / skipped RSS: default fetch_ttl_hours = 24 reuses the last snapshot. Use --reddit-refresh or set [reddit].fetch_ttl_hours = 0.

  • Optional OAuth (script app): set in the environment (never TOML): ROLLUP_REDDIT_CLIENT_ID, ROLLUP_REDDIT_CLIENT_SECRET, ROLLUP_REDDIT_USERNAME, ROLLUP_REDDIT_PASSWORD. When all four are set, Rollup uses oauth.reddit.com JSON first. Same ~/.config/rollup/env file as LinkedIn cookies.

  • Reddit-only run (--folder reddit:feed and no mbox): fetch failure is exit 1, not partial.

  • Dry-run / Settings GET: never contacts Reddit.