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.
The general upgrade
Section titled “The general upgrade”Running a published image — change the pinned version and pull:
SUMISURA_VERSION=v0.4.0docker compose -f docker-compose.release.yml pulldocker compose -f docker-compose.release.yml upYour data/ and output/ are mounted volumes, so an upgrade never touches
them.
Running from a checkout:
git fetch --tagsgit checkout v0.4.0 # whatever the newest release isdocker compose build # rebuild the imagesdocker compose upThen, if the release notes mention the skill or the extension:
claude plugin install sumisura@sumisura-local # reinstall the skilland 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.
Restoring an export
Section titled “Restoring an export”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:
cd backendgo run ./cmd/restore-records -data-dir ../data export.zip # report onlygo run ./cmd/restore-records -data-dir ../data -write export.zip # applygo run ./cmd/restore-records -data-dir ../data -write -overwrite export.zip # also replace existing filesA 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.
Migrating records
Section titled “Migrating records”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:
cd backendgo run ./cmd/migrate-records -data-dir ../data # report onlycp -r ../data ../data-backup # these files are not in gitgo run ./cmd/migrate-records -data-dir ../data -write # applyExit 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 sentThese 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.
Version-specific notes
Section titled “Version-specific notes”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:
cd backendgo run ./cmd/migrate-records -data-dir ../data # report onlygo run ./cmd/migrate-records -data-dir ../data -write # applyIt 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.
0.2.0 — Master Data became local-only
Section titled “0.2.0 — Master Data became local-only”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.
0.2.0 — prebuilt images, one port
Section titled “0.2.0 — prebuilt images, one port”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.
0.1.0 — first public release
Section titled “0.1.0 — first public release”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.