In May I wrote Codex, Three Months In. Codex had just replaced Komga on my server, and the big new thing was release tracking.

Since then: a 2.0 release, a new way of tracking reading progress, support for running several instances, and a native iOS client that’s almost ready for the App Store. Here’s what mattered.

Reading progress that doesn’t go backwards

The most important change is one you’d never see in a screenshot.

Codex used to store reading progress as “where you stopped” for each book. That’s how most readers work, and it breaks as soon as you read on two devices. Read to page 60 on your phone, then let an old browser tab sync “page 20” a minute later, and your progress jumps back. The last device to sync wins, even when it’s wrong.

Now Codex keeps a log of reading sessions and works out your progress from that log, ordered by when you actually read, not by when a device happened to sync. Going back to re-read a chapter still works, because it’s about time, not the highest page number. And a device only reports a position it actually reached, so a forgotten tab can’t drag you back.

A nice side effect: with a real history of reading sessions, reading stats came almost for free. Time read, pages, books finished, and a calendar heatmap.

Codex 2.0 was about configuration

People expect a major version to mean a big rewrite. Codex 2.0 was a small, deliberate break in one place: configuration.

For some settings, environment variables quietly lost to the config file, which is backwards from what anyone running it in Docker expects. 2.0 fixed the order and renamed the environment variables so they’re unambiguous. The part I’d defend hardest: Codex refuses to start if it sees an old variable name. Silently ignoring them would have meant the wrong port or default rate limits with no warning. To make the upgrade painless, the release before 2.0 shipped a command that lists exactly what would break.

Running more than one instance

2.0 also made Codex safe to run as several instances behind a load balancer, for example on Kubernetes.

The bug that forced this was almost funny: login with an external identity provider failed every single time with two instances. The login started on one instance and finished on the other, and the first instance kept its login state in memory. That state now lives in the database. Live updates got the same treatment: an edit on one instance now reaches people connected to the others.

Around the same time, Codex gained a tool to migrate from SQLite to PostgreSQL, and optional OpenTelemetry tracing.

Getting ready for an iOS app

The last post ended with “an iOS app to replace Komic” on the roadmap. That became Codex Reader, a native SwiftUI app with its own repository, and it’ll get its own post when it ships.

Before writing any Swift, I listed everything the app would need from the server and checked it against Codex. Only one thing was actually missing: resumable downloads of book files. Everything else existed. What didn’t hold up was the description of the API. The app generates its client from Codex’s OpenAPI spec, and that spec had enough mistakes that a generated client couldn’t trust it. A lot of the 2.x work was making the spec accurate, plus a check that flags any change that breaks the API.

Better on the web too

The web app kept improving alongside:

  • Offline reading in the installable web app, with progress saved while offline and synced later, plus a proper mobile layout.
  • Collections, read lists and want-to-read, including collections built from rules.
  • Search with a fuzzy index and an advanced search page with saved filter presets.
  • A better reader: transitions between chapters with auto-advance, swipe paging, and zoom.
Advanced search: nested filters, presets and relevance sorting.

Advanced search: nested filters, presets and relevance sorting.

The home dashboard on a phone.

The home dashboard on a phone.

Plugins

Two new plugins matter:

  • Tsundoku releases: Codex now gets release information from Tsundoku, my discovery service, which in turn reads Codex to know what I own.
  • MangaBaka recommendations: no account needed, based on similar tags and what other readers of a series also liked.
Recommendations, with per-user filters.

Recommendations, with per-user filters.

AI recommendations moved out of Codex

The last post promised “an AI-powered recommendation plugin”. It didn’t become a plugin. It became its own service, Shisho.

Shisho is an MCP server, so the AI part is whatever model I connect to it, usually Claude. It ties the other pieces together: Codex says what I own, what I’ve read and how I rated it; Tsundoku says what’s out there that I don’t have yet; MangaBaka fills in the metadata. Shisho does all the matching between those sources itself, and leaves the model to do the part it’s good at: reasoning about taste.

It keeps a taste profile that the model can propose changes to and I approve, and it logs every recommendation so I can say later whether it was a good one. Those verdicts feed back into the next round.

Shisho isn’t public yet. It probably deserves its own post.

What a real library teaches you

The bugs I learned the most from were ones no test fixture would catch. Test data has series with three books. Real libraries have series with hundreds or thousands of volumes, where marking a whole series as read was slow enough to notice. An archive with one corrupt image made every page after it show the wrong picture, which looked exactly like a bad scan. Daily reading found them. Tests didn’t.

What’s next

  • Codex Reader on the App Store.
  • A way for Codex to tell admins about new releases from inside the app.
  • More release sources and metadata providers.

The source is on GitHub, the docs are live, and the first post still explains why I built it.