Claude Transcripts docs GitHub
Work in progressUnder active development — not tested as ready for use. Breaking changes land without notice, stored data may need to be discarded between revisions, and there is no auth or security model. These docs describe the intended design as much as the current state.

28. External vs bundled Meilisearch

Date: 2026-07-30

Status

Accepted (2026-08-07): option 2, external and namespaced — see Decision. The bundled instance remains the default and the configuration we test; pointing at an external one is now safe rather than merely likely to work.

Context

The backing services differ in how portable they are, and that difference is not obvious until you try to move one.

CouchDB and Garage are addressable stores. Point COUCHDB_URL / S3_ENDPOINT at somewhere else — a NAS, a managed CouchDB, another host's Garage — and the app works. They hold state we send and give back what we ask for; the app doesn't care where they run. Running them externally is already supported: the bundled stack is a convenience, not an assumption (ADR 0020).

Meilisearch is not a store — it's a derived index, and that makes it different in kind. Nothing in it is authoritative: everything is a projection of CouchDB (ADR 0009). Making it external means exporting more than a URL:

So the awkwardness is real and worth naming: for the stores, "external" is a URL; for the index, "external" is a shared, stateful, configured dependency with a lifecycle we partly drive.

There's a further wrinkle: ADR 0009 anticipates indexing sources beyond this stack (GitHub, git history, external docs). That future argues for a shared external instance — which is exactly the case that's hardest to isolate.

Options

  1. Bundled only (status quo). Meilisearch stays part of deploy/, no auth, localhost. Simple, isolated, disposable; rebuilds are safe because nothing else uses it. Rules out reusing an existing instance.
  2. External, namespaced. Support MEILI_HOST + MEILI_API_KEY pointing anywhere, and prefix index names per deployment (e.g. <instance>_sessions). Removes the collision and makes a destructive rebuild safe. Costs a config surface (instance id), a migration for existing index names, and key/permission handling.
  3. External, bring-your-own-index. The operator creates and configures the indexes; we only read and write documents, never settings, and never clear. Safest for a shared instance, but moves setup burden onto the operator and makes our settings documentation normative rather than executable.
  4. Pluggable search backend. Treat Meilisearch as one implementation behind a small interface (index/search/rebuild), so an external engine — or Typesense, or none — is a config choice. The most flexible and the most work; only worth it if a second backend is actually wanted.

Decision

Adopt option 2: external, namespaced.

What settled it wasn't the multi-tenancy question in the abstract — it was noticing an inconsistency the codebase already contained. CouchDB databases and S3 buckets are named in config/ as keyed maps (claude-transcripts-sessions), deliberately, because "the app supports multiple databases and buckets". Meilisearch's indexes were bare hard-coded constants: "sessions" and "turns".

Those are the most collidable names imaginable, and reindex clears the index before rebuilding. Anyone who set MEILI_HOST to an existing Meilisearch — which the config has always allowed — could destroy someone else's sessions index by running a supported command. The dangerous option wasn't "go external"; it was the status quo, which permitted going external while quietly assuming nobody would.

So the index names move into config/ as meilisearch.indexes, exactly like the databases and buckets, defaulting to claude-transcripts-sessions and claude-transcripts-turns. That is the whole of option 2 that Tier 1 needs:

Deliberately not adopted:

Migrating an existing instance

Nothing in Meilisearch is authoritative, so there is no data migration. An instance running before this change has indexes literally named sessions and turns; after it, the app reads and writes the namespaced names. Run claude-transcripts reindex to populate them, then delete the two old indexes if you want the space back. A deployment whose config.json predates the meilisearch key gets the defaults, so it keeps working without being edited.

Consequences