Skip to content

os migrate plan prints 4 ERROR records with stack traces on a not-yet-migrated database — every one of them is a read whose caller treats absence as a normal answer #13273

Description

@os-trump

Found while working #13204; out of that card's scope, filed unassigned for triage.

Measured

Built CLI, os migrate plan against a sqlite database whose platform tables have
not been created yet — the ordinary state of a first run, and exactly the run the
command exists to describe. Fixture: an objectstack.config.ts composing
SecurityPlugin, a control DB carrying a handful of tables, no compiled artifact.

The command succeeds: exit 0, a correct plan (14 table(s) to create, 32 column(s) to add). On the way there it emits, at ERROR level, with full
stack traces:

4 x ERROR Find operation failed
2 x "object":"sys_metadata"
1 x "object":"sys_metadata_activation"
1 x "object":"sys_migration"
4 x [sql-driver] DATABASE_ERROR - the backend refused a read on '<name>' (SQLITE_ERROR) ... no such table

Call sites reached, from the stacks: announceOpenMigrationGates ->
readMigrationFlagVerified (sys_migration), ObjectStoreActionActivationStore.probe
(sys_metadata_activation), readAuthoredTranslationLayer and
ObjectQLPlugin.readAuthoredHookRows / readAuthoredActionRows (sys_metadata).

Why this looks like a defect rather than a true report

Each of those callers treats the failure as a normal, expected answer, and
says so in its own code:

  • engine.ts on the migration flag: "no sys_migration object registered, no
    row, an unreadable table, a ... -> false"; it names an unreadable table as one
    of the inputs its verdict is defined over.
  • metadata-activation-store.probe already follows its ERROR with a WARN that
    states the consequence in operator terms — so the ERROR beneath it adds a
    stack trace and no information.
  • the authored-hook / authored-action re-syncs report authoredRows: 0 and
    carry on.

So the ERROR channel fires for a condition every consumer downstream of it
handles as routine. AGENTS.md already names this failure mode in
sql-driver.ts's own comment on a nearby line: "the over-application AGENTS.md
warns trains everyone to skim error". On os migrate plan the cost is
concrete: the first thing an operator sees when planning an unmigrated database
is four stack traces, in a command whose header documents it as a dry run that
writes nothing.

Not in scope of #13204, and not caused by it

The records come from @objectstack/objectql and @objectstack/core boot
paths, not from the migrate command. They are present on origin/main at
74049254 with no part of #13204 applied — this is a pre-existing shape that
#13204's fixture happened to put in front of a reader.

Open questions for triage

  1. Is the right fix at the READ site (a find that tolerates a missing table
    for these probe-shaped reads and returns "unknown" without logging ERROR), or
    at the LOG site (demote to debug when the driver's own classification is
    "relation does not exist")?
  2. Does the same shape appear on os serve first boot, where nobody is reading
    for a dry-run verdict? Not measured here.

Reproduce

os migrate plan --database-url file:/path/to/an-unmigrated.db

in a project directory with an objectstack.config.ts, NODE_ENV=production.

Generated by Claude Code

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions