Skip to content

objectql: engine.insert logs Insert operation failed at ERROR inside a catch that rethrows — the case AGENTS.md names as "not a degradation at all" #17052

Description

@os-bill

packages/objectql/src/engine.ts logs a failure it is about to hand back to its caller, at ERROR. AGENTS.md names that exact case and forbids it.

⛔ Filed by the seat that hit it while repairing the publish-smoke boot gate (see the acceptance notes on PR #17051). It is out of that card's file surface and it reverses nothing recorded, so it is a card rather than a rider. domain:* and priority:* are triage's to set — deliberately not set here.

The site

insert's catch, packages/objectql/src/engine.ts (the line that emits Insert operation failed):

const logged = e instanceof DuplicateRecordError ? e.cause : e;
this.logger.error('Insert operation failed', redactBoundStatement(logged) as Error, { object });
throw e;

The rethrow is the next statement. Update operation failed in the sibling verb has the same shape, and packages/objectql/src/engine-strict-readonly-warning-truthful.test.ts pins the level (l.level === 'error'), so the class is at least two sites and is pinned.

The contract it violates

AGENTS.md, Degradation log levels — warn vs error, verbatim:

And a failure handed to the CALLER is not a degradation at all — the third legal answer: a catch that answers errorFromThrown(e, 400), or a batch whose contract IS a per-item outcome report, does not look normal from the outside — the requester was told. Do not bolt a logger.error onto such a site; declare how it delivers instead — FAILURE_PROPAGATION_CALLEES (repo-wide names) or the function-scoped FAILURE_PROPAGATION_SITES in the checker, which then proves structurally that every path out of the catch delivers.

The requester was told: e is rethrown one line down, and since #14095 the door hands the caller a DuplicateRecordError envelope. pnpm check:durability-log-level does not catch it because its log-level rule fires on a catch that logs below error without rethrowing — the opposite direction.

What it cost, measured

A caller that catches the exception makes the ERROR line a false alarm that nothing downstream can distinguish from a real one.

@better-auth/oauth-provider@1.7.2 seeds sys_oauth_resource in insertOnly mode and documents the identifier UNIQUE constraint as its race-safety mechanism — "one wins, the other catches the constraint error and treats it as a no-op" (dist/introspect-C6P1zrTr.mjs, the docblock over seedResources, read from the installed tarball). Its catch tests the message for unique|duplicate|UNIQUE, logs the collision at debug, and continues. Our line is emitted before that catch ever runs, so a completely healthy first boot of every fresh create-objectstack project prints:

ISO_TIMESTAMP ERROR Insert operation failed {"object":"sys_oauth_resource","error":{"message":
  "UNIQUE constraint failed: sys_oauth_resource.identifier [statement and bound values redacted]", ...}}

That line is what red-lit publish-smoke / packed-tarballs on main for six consecutive runs and blocked a release (#17027) — on a candidate whose auth and CRUD probes were, once allowed to run, all green. The gate has been repaired to judge the boot by predicate rather than by level (PR #17051), so this card is no longer release-blocking; the misleading line is still emitted on every fresh boot.

What this card does NOT decide

⛔ It does not prescribe the level, because the diagnosis the line carries is load-bearing and was deliberately shaped twice:

Neither decided the level. The three shapes worth weighing:

  1. Declare the propagation — add the site to FAILURE_PROPAGATION_SITES / FAILURE_PROPAGATION_CALLEES and drop the level to warn or debug, which is what the contract text prescribes. The diagnosis survives at a level a healthy run may legitimately emit.
  2. Keep ERROR and narrow it — log at ERROR only where the caller cannot be told, e.g. only when the throw is swallowed upstream. Needs a way to know that, which the site does not have today.
  3. Keep ERROR as-is and record the decision in an ADR, so the next seat that reads AGENTS.md's rule against this exact shape finds the exemption instead of re-filing this card.

Not measured here

  • ⛔ How many other rethrowing catches in packages/objectql log at ERROR. Only insert and update were established; the class was not swept.
  • ⛔ Whether any consumer greps for the ERROR level of these two lines. The pinned test asserts the level, but no downstream reader was surveyed.

Generated by Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions