Skip to content

Upgrading

One version covers the whole product — backend, frontend, extension and plugin. They are only guaranteed to work together at the same commit, so they ship together.

Before 1.0, a breaking change bumps the minor version, and “breaking” means you have to do something: a renamed environment variable, a record migration, a removed route, a plugin reinstall. Release notes say so explicitly.

Running a published image — change the pinned version and pull:

.env
SUMISURA_VERSION=v0.4.0
Terminal window
docker compose -f docker-compose.release.yml pull
docker compose -f docker-compose.release.yml up

Your data/ and output/ are mounted volumes, so an upgrade never touches them.

Running from a checkout:

Terminal window
git fetch --tags
git checkout v0.4.0 # whatever the newest release is
docker compose build # rebuild the images
docker compose up

Then, if the release notes mention the skill or the extension:

Terminal window
claude plugin install sumisura@sumisura-local # reinstall the skill

and reload the unpacked extension in your browser.

Back up first if you are nervous. data/jobs/ and data/applications/ are not in git — use Export in the app, or copy the data/ directory. Restoring an export below puts that zip back.

The zip from Export holds jobs/, applications/ and pending-captures/. To put it back on a new machine, or after losing data/, use the offline restore command. It is a dry run unless you pass -write:

Terminal window
cd backend
go run ./cmd/restore-records -data-dir ../data export.zip # report only
go run ./cmd/restore-records -data-dir ../data -write export.zip # apply
go run ./cmd/restore-records -data-dir ../data -write -overwrite export.zip # also replace existing files

A file that already exists is never touched unless you pass -overwrite. An entry outside those three directories, or with .. or an absolute path in its name, makes the command fail before anything is written. Exit codes: 0 nothing pending, 3 a dry run found records to restore, 1 an error, 2 a bad flag or missing zip.

If the zip comes from an earlier version, run the migration below afterwards.

Some releases add fields to Job Listing and Application records. Records written earlier read as legacy and keep working, but a one-shot migration brings them current. It is a dry run unless you pass -write:

Terminal window
cd backend
go run ./cmd/migrate-records -data-dir ../data # report only
cp -r ../data ../data-backup # these files are not in git
go run ./cmd/migrate-records -data-dir ../data -write # apply

Exit codes: 0 nothing pending, 3 a dry run found work to do, 1 an error.

It only backfills what is provably already on disk, and names anything it cannot know rather than inventing it. Every record is read and validated before anything is written, so a corrupt or newer-than-expected record stops the run with nothing changed.

Postings held by more than one Job Listing

Section titled “Postings held by more than one Job Listing”

The same run also reports any posting your corpus holds twice — two Job Listings whose URLs point at the same posting, which was possible before saving started refusing a posting you already track. It prints each one’s file, Job Title, saved date and Application Status, oldest first:

Postings held by more than one Job Listing (reported, never resolved):
linkedin:4012345678
jobs/acme.md Backend Engineer saved 2026-09-08T09:14:07Z status saved
jobs/acme-2.md Backend Engineer saved 2026-09-11T10:00:00Z status sent

These count as pending work, so a dry run that finds one exits 3. -write never resolves them. Merging would mean choosing which Status history, which Notes and which Generations survive, and that is your call, not a tool’s: keep the record you want and archive or delete the other yourself. A -write run therefore completes normally (exit 0) and goes on reporting them until you do.

Application records gained an optional follow-up date (schema version 3). It has nothing to backfill: the migration only stamps the new version, and applications without a date keep the two-week stale rule.

0.4.0 — one Job Listing per posting, and a card that knows what you track

Section titled “0.4.0 — one Job Listing per posting, and a card that knows what you track”

Two things to do, in this order.

Run the record migration. Job Listings now record where the role is, so records written before this release are a schema version behind:

Terminal window
cd backend
go run ./cmd/migrate-records -data-dir ../data # report only
go run ./cmd/migrate-records -data-dir ../data -write # apply

It stamps them forward and fills nothing in — nothing on disk says where an already-saved role was, and guessing it from the Job Description would write a guess down as a fact, so the dry run names location as unknowable for each record instead. Add one by hand on the Job Listing’s page wherever you want it. The app works untouched without this; running it is what makes an absent location mean “none” rather than “unknowable”.

The same run reports any posting your corpus already holds twice (see Postings held by more than one Job Listing above), because saving now refuses a posting you already track.

Reload the browser extension. Its manifest.json changed — it gained a capture shortcut (Alt+Shift+S, rebindable at chrome://extensions/shortcuts) and host permissions for the two job boards it already ran on, which the shortcut needs to reach the page. An unpacked extension keeps working until you reload it, but the shortcut silently does nothing until you do.

Nothing else needs action: the capture button became a card, which asks your own backend about the posting you have open before you click. No new environment variable, and no change to how the app is run.

All Master Data is now gitignored, not just data/profile.yaml: data/experience/, data/projects/ and data/cover-letter-snippets/ joined it, and the stubs moved to data/examples/ (ADR-0038).

Your files are read from the same paths as before, so nothing moves on your disk and nothing needs regenerating. After pulling, git simply stops tracking them. Two things to know:

  • If your own Master Data was committed to a fork or a private remote, the history is still there. Removing it takes a history rewrite, not this upgrade.
  • data/ is now entirely yours to back up. A private repo or a synced folder covers it.

Releases now publish ghcr.io/gio-del/sumisura:<version> (amd64 and arm64), where the Go backend also serves the production frontend build — one container, one port, no CORS (ADR-0039). Self-hosting no longer needs Node, Go or a checkout: take docker-compose.release.yml, add .env and data/, and run it.

Nothing is forced on you. docker compose up from a checkout is unchanged and still hot-reloads through Vite. If you move to the image, note that the app is on 8080, not 5173, and that LAN mode’s token now guards /api/* rather than every path — the frontend’s own files load without it, since a browser cannot attach a header to the request that loads a page.

Nothing to migrate. If you were running the project before it was renamed from CV Reporter, see the rename notes in the release: environment variables changed from CV_REPORTER_MODEL_* to SUMISURA_MODEL_*, the LAN header became X-Sumisura-Token, and the plugin is now installed as sumisura@sumisura-local and invoked as /sumisura:tailor-cv.