release: v1.0.0, and split the README into docs/

The archive does what it was built to do, unattended, on both machines:
four providers, incremental and resumable, Markdown plus Joplin, on a
schedule, with results pushed to a phone and failures that announce
themselves. The one item left on the roadmap — a StartOS service that
consolidates both machines into one corpus — is an optional convenience,
not a gap. Hence 1.0.0 rather than another 0.x.

The code in this release shipped across b6ce636..8c3e00a: the claude.ai
403 session-invalid fix, the fork-subagent recursion fix, the FAILED-push
backstops on both platforms, and asset-download retries with cross-run
recovery, a circuit breaker and media counts in the notification. This
commit cuts the release and pays a documentation debt.

The README had reached 939 lines / 6,490 words / 48 KB / 96 headings,
doing four unrelated jobs at once, and FUTURE.md asked for the split to
ride along with the next substantial README edit rather than break every
existing anchor for its own sake. A release is that edit. README.md is
now 303 lines and keeps only what you need to install and configure the
tool; the rest moved verbatim into docs/providers.md, docs/cli.md,
docs/scheduling.md and docs/troubleshooting.md. No section was rewritten
or dropped and every original heading still exists, so each anchor still
resolves somewhere — but bookmarks into moved sections now need the
docs/ path, which is the cost this deferral was protecting against.

.gitignore needed the fix FUTURE.md predicted: the blanket *.md rule
(exported conversations are Markdown and may be private) matched all
four new pages, and git check-ignore confirmed every one would have been
silently ignored. !docs/*.md is added with a comment saying why. Gitea's
rendering of relative docs/ links cannot be checked before pushing, so
it still needs confirming after.

Also corrects two stale claims in the changelog: the Windows backstop is
no longer "not yet run under Windows PowerShell 5.1 or Task Scheduler"
(confirmed on winmini today, LastTaskResult 0 — only the FAILED push
itself remains unexercised there), and the test count is 414, not 409.

414 tests passing. No code changes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012gCWSknsWvhLnMFwa7qnGp
This commit is contained in:
JesseMarkowitz
2026-10-20 22:18:37 -04:00
co-authored by Claude Opus 5
parent 8c3e00a520
commit 04ebb8987c
9 changed files with 750 additions and 714 deletions
+25 -4
View File
@@ -3,7 +3,17 @@
All notable changes to this project will be documented here.
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased]
## [1.0.0] - 2026-10-20
The archive does what it was built to do, on both machines, unattended: four
providers, incremental and resumable, Markdown plus Joplin, on a schedule, with
results pushed to a phone and failures that announce themselves. What remains on
the roadmap is one optional consolidation (`FUTURE.md` §1), not a gap — so this
is 1.0.0 rather than another 0.x.
This release is mostly the unglamorous half of that claim: four bugs that each
made a scheduled run lie about its own outcome, and the documentation split that
939 lines had outgrown.
### Fixed
- **A transient 5xx on an image download lost the image permanently, and a timeout lost the whole conversation.** An asset download is two hops: `GET /files/{id}/download` mints a signed URL, then that URL is fetched for the bytes. Only the first went through `_make_request`. The second was a bare `session.request` with no 5xx handling at all, which produced two distinct failures in the 2026-10-20 09:00 run:
@@ -31,9 +41,20 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- **A media circuit breaker, so a bad day at the provider costs one quiet morning.** Five consecutive *transient* asset failures stop downloads for the remainder of the run; the skipped assets are recorded as retryable rather than abandoned, so the next run collects them. Permanent failures never count toward tripping it — a deleted upload says nothing about the provider's health, and this archive holds 19 of those that would otherwise trip it on a perfectly healthy server. Any success resets the streak, so ordinary scattered losses never trip it either. Assets already on disk are still linked while the breaker is open, since that needs no network.
- **Media failures appear in the push notification.** `format_summary` now adds `N media failed` per provider. The 2026-10-20 run lost two assets and pushed a clean "AI archive OK" with no hint of it — the counts existed only in the log file, which is somewhere you have to think to go and look. Counts only, never asset ids, for the same reason conversation titles are excluded: a topic on public ntfy.sh is readable by anyone who knows its name. Media failures deliberately do **not** raise the notification's priority or mark the run failed — the transcript is archived and a transient asset recovers by itself.
### Changed
- **The README is split into a short overview plus four documents in `docs/`,** closing `FUTURE.md` §2. It had reached 939 lines / 6,490 words / 48 KB / 96 headings — a ~25-minute read doing four unrelated jobs at once, and still growing (it was 830 / 5,562 / 37 KB / 61 when the split was first proposed). `README.md` is now 303 lines and keeps only what you need to install and configure the tool: the ToS warning, installation, `doctor`, `.env` reference, output structure, security notes. The rest moved verbatim — no section was rewritten or dropped, and every original heading still exists, so every anchor still resolves *somewhere*:
| Document | Content |
|----------|---------|
| `docs/providers.md` | Session tokens, lifetimes and expiry detection, the `auth` wizard, ChatGPT Projects, Claude Code sessions, Codex sessions |
| `docs/cli.md` | CLI reference — all eleven commands |
| `docs/scheduling.md` | systemd timer, Windows task, what to know before relying on it, notifications |
| `docs/troubleshooting.md` | How the cache works, every failure message, media download failures, filing a bug report |
The cost this deferral was protecting against is real and is now paid: a bookmark or note pointing at a README anchor that moved (`#cli-reference`, `#troubleshooting`, `#getting-your-session-tokens`, …) no longer resolves, because the anchor is in another file. The three questions `FUTURE.md` left open are settled as follows. **`.gitignore` did need the fix it predicted** — the blanket `*.md` rule (exported conversations are Markdown and may be private) matched all four new pages, and `git check-ignore` confirmed every one would have been committed-never and ignored silently; `!docs/*.md` is added alongside the existing per-file re-includes, with a comment saying why. **Cross-file anchors are used, not avoided**, but only for the four links that already existed between these sections, each verified to point at a heading that still exists. **Gitea rendering of relative `docs/` links is unverified** — it cannot be checked before pushing, so confirm it after.
### Notes
- **`FUTURE.md` §2 (split the README) is still open and was deliberately not done here.** It asks to be folded into "the next substantial README edit", and this edit is a new subsection plus two cross-references — not substantial enough to justify breaking every existing link into a README anchor. The new material is written to survive the split: it lives under *Embedded images and media*, which that plan assigns to `docs/troubleshooting.md`.
- Verified live rather than only in tests: the two assets lost in the morning run were re-downloaded successfully through the new path (`doctor` all green, 409 tests passing, a read-only `export --dry-run --provider chatgpt` exercising the refactored request core across 4 listing pages / 379 conversations). The cross-run retry and the breaker are covered by unit tests only — reproducing a multi-day storage incident against the live provider is not something this change waited for.
- Verified live rather than only in tests: the two assets lost in the morning run were re-downloaded successfully through the new path (`doctor` all green, 414 tests passing, a read-only `export --dry-run --provider chatgpt` exercising the refactored request core across 4 listing pages / 379 conversations). The cross-run retry and the breaker are covered by unit tests only — reproducing a multi-day storage incident against the live provider is not something this change waited for.
- **Every scheduled Claude Code sync since 2026-09-24 died with `RecursionError`.** Claude Code's `fork` subagents write a transcript that opens with a copy of the parent turn that spawned them — the fork's own `Agent` call included. `_extract_messages` folds a subagent inline whenever it meets its spawn call, so it met that copy inside the fork, folded the same fork again, and recursed until Python's limit. One fork anywhere in `~/.claude/projects` was enough to fail the whole provider; seven existed across three sessions, and the Codex half of the run, unaffected, hid the cause behind a generic exit 1.
@@ -49,7 +70,7 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
The Windows task gets the same backstop. Its action is now `scheduling\run-sync.ps1` (via `powershell.exe -File`, window hidden) in place of the inline `cmd.exe` loop, with the same rules: every provider attempted, exit 1 if any failed, a FAILED push carrying the class only for a run that died unreported, `NTFY_NOTIFY=off` honoured, `NTFY_*` read from the environment first. Because Task Scheduler keeps no output, each run's output is appended to `cache\logs\scheduled-sync.log` (one rollover to `.1` past 1 MB), and the push points there. The wrapper sets `PYTHONIOENCODING=utf-8`: with stdout redirected to a file rather than a console, Python would otherwise encode in the ANSI code page and fail on the first `✓`. Re-run `Register-AiChatSyncTask.ps1` with your providers to pick it up.
Verified on Linux under PowerShell 7.6 with a stand-in `cmd.exe`, the same fake launcher and capture server: the same five outcomes as above, plus a bearer token, an environment `NTFY_TOPIC` overriding `.env`, an unreachable server (warning, exit still 1) and the `chatgpt,claude` single-argument form. Not yet run under Windows PowerShell 5.1 or Task Scheduler.
Verified on Linux under PowerShell 7.6 with a stand-in `cmd.exe`, the same fake launcher and capture server: the same five outcomes as above, plus a bearer token, an environment `NTFY_TOPIC` overriding `.env`, an unreachable server (warning, exit still 1) and the `chatgpt,claude` single-argument form. Since confirmed on winmini under Windows PowerShell 5.1 and Task Scheduler (2026-10-20): `Register-AiChatSyncTask.ps1 -Provider chatgpt,claude` registered the wrapper, and a `Start-ScheduledTask` run of it completed with `LastTaskResult: 0`, writing both providers' output to `cache\logs\scheduled-sync.log`. Registering is a one-time write, so a `git pull` does **not** update an already-registered task — re-run the registration script after taking this change. The one path still unexercised on Windows is the FAILED push itself: it needs a run that dies without reporting, which has not happened there yet.
- **An expired Claude session key reported a raw JSON dump instead of how to fix it.** `_make_request` routed only **401** to the auth handler (`src/providers/base.py`), and claude.ai does not use 401 — an invalid or expired `sessionKey` comes back as `403 permission_error` with `details.error_code = account_session_invalid`. So the one message that names the cookie, its ~30-day lifetime and the DevTools path to refresh it could never fire for Claude. What the user got instead was the generic 4xx path: `HTTP 403 — error: {'type': 'permission_error', 'message': 'Invalid authorization'…}`, which reads like a permissions problem with the account and not like "your key expired, here is how to replace it."