Skip to content

A config symlink makes every omo migration fail on start

This is a finding

Something I found or learned, or a discovery that cost time once: cheap by design, and true of a moment rather than in general.

Every opencode start printed the same warning, naming a file nobody had asked it to write:

Migration target is not an omo config path: /home/felix/.config/opencode/omo.jsonc.PROFILES

The plugin accepts a migration target only when its resolved path is literally ~/.omo/omo.json, ~/.omo/omo.jsonc, or the same two filenames inside a project's .omo/ directory. Felix's ~/.omo/omo.jsonc is a symlink into the opencode config repository, where the file is called omo.jsonc.PROFILES so that a single checkout can carry several profile sets. The symlink is followed before the check, so the directory is wrong and the basename is wrong, and the write is refused.

Reading the config was never affected, which is why the arrangement had worked for a month. The rejection is in the writer alone.

What was trying to write

A migration whose work had already been done by hand. ~/.omo/.migration-journal.json held 2026-09-category-deep-split with targetWritten: false, left behind by the upgrade that split the deep category in two. The rename had been applied to the config directly, but the _migrations array in that file still listed only the two older markers, so the plugin saw an unapplied migration, tried to apply it, and hit the symlink. Every start, identically.

The journal is worth opening rather than deleting on sight. Its stored targetWrite payload is a snapshot of what the config looked like when the migration was first attempted, and in this case several model pins had been edited since. Had the path problem been fixed and the migration allowed to run in its replace-target mode, it would have written the stale snapshot back over a fortnight of later edits. A repair that makes the write succeed is the more dangerous of the two available repairs.

Both the journal-resume path and the ordinary plan path check for the migration's marker in the target document before anything validates the target path. Recording the migration as applied therefore stops the attempt early enough that the symlink is never examined:

"_migrations": [
  "2026-07-opencode-config-unification",
  "2026-08-reasoning-unification",
  "2026-09-category-deep-split"
]

Deleting ~/.omo/.migration-journal.json removes the resume attempt as well. After both, a real opencode run starts clean and the journal does not reappear.

Reversing the symlink would be the fix that addresses the cause, by putting the real file at ~/.omo/omo.jsonc and pointing the repository copy at it. That is not available where the repository layout requires the differently-named file to be the real one, and the marker repair costs one line.

What it leaves behind

The next migration oh-my-openagent ships will attempt the same write and fail the same way, with a different migration id in the message. The repair is the same each time: confirm the change is already present in the config, add the marker, clear the journal.

That recurrence is the actual finding. A warning that returns on a schedule set by someone else's release cadence reads as noise by the third occurrence, and the one time it names a migration that has not been applied by hand is indistinguishable from the times it does not.

The following pages link to this page: