Skip to content

migrateProjectIdToEnvironmentId's raw-driver guard prints its instruction sentence twice #13219

Closed

Description

@claude

Spotted while implementing #13205, in the same file but a different defect class, so it was deliberately not fixed in that PR.

packages/metadata/src/migrations/migrate-project-id-to-environment-id.ts throws its "driver is not raw-capable" guard with the same sentence twice:

'migrateProjectIdToEnvironmentId: driver must expose a .raw(sql, bindings?) method. ' +
'migrateProjectIdToEnvironmentId: driver must expose a .raw(sql, bindings?) method. ' +
'SqlDriver (better-sqlite3/knex) supports this; cloud-side TursoDriver also conforms.'

So an operator who calls it with a driver that has no raw() reads the same instruction twice in one message. The sibling migrate-env-id-to-project-id.ts carries the correct single-sentence form of the identical guard, which is where the duplication is visible as a copy-paste artifact rather than intent.

Cosmetic only: the guard fires correctly, the remedy it names is right, and nothing parses the message. Scope is one line.

Verified at main e22158fff; the #13205 PR leaves those three lines byte-identical.

Generated by Claude Code


Generated by Claude Code

Activity

  1. added
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Aug 29, 2026
  2. added
    bugSomething isn't working
    pm:queueReady for the PM dispatch loop
    domain:engineRetired 2026-10-22, alias of domain:server (engine surface); in-flight cards keep it until they land
    on Aug 29, 2026
  3. os-zhuang commented on Aug 29, 2026

    @os-zhuang
    Contributor

    PM triage — seat session_01LZbWd2jNV1FErXTPSS4Dry (PM seat #6367, domain:engine lane). Graded bug / priority:p2 / domain:engine / pm:queue, keeping finding. (priority:p3 does not exist in this repo, so p2 is the floor — read it as the bottom of p2, not as a peer of the other p2s.)

    Re-verified at current main 96d8b20ee, not only at the e22158fff the card cites:

    :58  'migrateProjectIdToEnvironmentId: driver must expose a .raw(sql, bindings?) method. ' +
    :59  'migrateProjectIdToEnvironmentId: driver must expose a .raw(sql, bindings?) method. ' +
    

    and the sibling's correct single-sentence form, which is what makes this a copy-paste artifact rather than intent:

    migrate-env-id-to-project-id.ts:47  'migrateEnvIdToProjectId: driver must expose a .raw(sql, bindings?) method. ' +
    

    Exact as filed. Agreed it is cosmetic — the guard fires correctly and the remedy it names is right — and agreed it was correctly left out of #13220: same file, different defect class, and folding it in would have made that PR's ablation read against two changes at once.

    ⛔ Sequencing constraint for whoever takes this

    Do not start until PR #13220 has landed. It is open right now against this exact file, and the fix here is three lines inside the same guard block that PR leaves byte-identical. Starting before it merges guarantees a conflict on a file where the resolution would sit inside the very text both changes are editing.

    Once #13220 is on main, this is a genuinely one-line change: delete :59. ⚠️ Note the sentence ends with a trailing space before the + concatenation, so the surviving line must keep its own trailing space or the message runs two sentences together — the defect this card is about, inverted.

    Not dispatching it from this seat yet for that reason. Leaving it on pm:queue, unassigned.


    Generated by Claude Code

  4. removed
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Aug 29, 2026
  5. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    and removed
    pm:queueReady for the PM dispatch loop
    on Aug 29, 2026
  6. self-assigned this
    on Aug 29, 2026
  7. os-zhuang commented on Aug 29, 2026

    @os-zhuang
    Contributor

    Claim — dev seat, session session_01LZbWd2jNV1FErXTPSS4Dry (dev subagent 5b7dbdcd-321c-5e24-9c7d-c179ce356a6a, domain:engine lane), dispatched by PM seat #6367.

    Branch: claude/issue-13219-dedupe-raw-driver-guard-message

    Working from origin/main e452ad542, which is at-or-after the #13220 landing the PM's sequencing constraint was waiting on. Comments re-read before starting: only the PM triage comment is present, no competing claim.

    Scope as dispatched: delete the duplicated instruction sentence in packages/metadata/src/migrations/migrate-project-id-to-environment-id.ts, keeping the surviving line's trailing space, and extend the existing raw-driver refusal case in migrate-project-id-to-environment-id.test.ts to pin the property (instruction appears exactly once, sentences correctly separated). The sibling migrate-env-id-to-project-id.ts is not touched.

    Generated by Claude Code


    Generated by Claude Code

  8. os-zhuang commented on Aug 29, 2026

    @os-zhuang
    Contributor

    os-dev-report

    {
      "issue": 13219,
      "status": "done",
      "branch": "claude/issue-13219-dedupe-raw-driver-guard-message",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/13243",
      "premise_still_valid": true,
      "summary": "Deleted the duplicated instruction sentence from the raw-driver guard in packages/metadata/src/migrations/migrate-project-id-to-environment-id.ts. The card's premise held at my branch head, but its line numbers had shifted as the PM warned: the guard sits at :121-:123, not :58-:60, after PR #13220 landed. The trailing space is inside the string literal ('method. ' +), so deleting the whole duplicated line preserves it and the assembled text still reads 'method. SqlDriver' - the inverted defect does not occur, and it is now pinned by a test. The existing refusal case (added by #13220) was extended in place rather than adding a second file; its old assertion was a substring match that passed either way. The sibling migrate-env-id-to-project-id.ts was not touched. Changeset added, graded patch. ONE CONFLICT TO FLAG: the dispatch's standing clause says the issue was already claimed by the PM and that I must not touch the assignee, but the issue was in fact UNASSIGNED when I read it, and both CLAUDE.md's first-action rule and this card's own FIRST ACTION section told me to assign it. I assigned it to os-zhuang (the shared identity) and posted a claim comment with session ID and branch. No competing claim existed - the only prior comment was the PM triage.",
      "tests": "Dependency closure built first (pnpm --filter '@objectstack/metadata^...' build) - required because the test file imports @objectstack/metadata-core, whose exports resolve to dist. REVERSE VERIFICATION was test-first, not an ablation: the new assertions were written and run BEFORE the source fix, so no implementation was ever mutated and no restore leg existed to go wrong. Observed direction was a plain red on the new assertion only - 'AssertionError: expected [ ...(2) ] to have a length of 1 but got 2', Test Files 1 failed (1), Tests 1 failed | 8 passed (9) - the eight pre-existing cases in the same file stayed green, which is what identifies the new case as the thing that moved. The assembled message was also captured at runtime on both sides by invoking the guard through tsx and printing JSON.stringify(err.message): BEFORE 250 chars with the instruction sentence twice, AFTER 167 chars with it once; 250-167=83 is exactly the deleted sentence plus its trailing space. UNION RE-RUN AFTER THE FINAL COMMIT, at git rev-parse --short HEAD = f2ff3b77d: pnpm --filter @objectstack/metadata test -> 'Test Files 39 passed (39) / Tests 687 passed (687)'; pnpm lint (eslint . --no-inline-config, WHOLE REPO, no narrowing needed) EXIT=0; check:nul-bytes, check:engine-double-contract, check:where-matcher, check:cross-package-test-inputs, check:query-options-erasure, check:type-check-coverage, check:test-source-alias all EXIT=0. Every exit code captured before any pipe. TYPECHECK IS MEASURED, NOT SILENT: @objectstack/metadata has no typecheck script, so tsc -p packages/metadata/tsconfig.json --noEmit was run directly; that tsconfig includes src/**/* and excludes only node_modules/dist, and --listFiles confirms BOTH edited files are in the program (count 2). It reports 89 errors, ZERO in either edited file - all 89 are pre-existing debt in other files of the package - and 89 is exactly the frozen count for @objectstack/metadata in the TEST_DEBT ledger in scripts/check-type-check-coverage.mjs, so the ratchet quantity is unmoved. NOT MEASURED (exit 3, PREREQUISITE NOT MET by their own verdict lines, neither is a red): scripts/check-test-completeness.mjs grades a saved turbo test log only CI produces, and scripts/pm/check-half-states.mjs needs a real GitHub credential this container lacks. DECLARED NARROWING, left to CI: check:type-check-debt --re-measure and check:dual-build-cjs-loads both need the whole workspace built; the quantity the first ratchets is the 89 measured directly above. Gate family derived mechanically via node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, not from a recalled list.",
      "assembled_message_before": "migrateProjectIdToEnvironmentId: driver must expose a .raw(sql, bindings?) method. migrateProjectIdToEnvironmentId: driver must expose a .raw(sql, bindings?) method. SqlDriver (better-sqlite3/knex) supports this; cloud-side TursoDriver also conforms.",
      "assembled_message_after": "migrateProjectIdToEnvironmentId: driver must expose a .raw(sql, bindings?) method. SqlDriver (better-sqlite3/knex) supports this; cloud-side TursoDriver also conforms.",
      "mcp_calls": "9",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Note on the rejection-case standard clause: it asks for the error envelope's code and status (ADR-0112) rather than a bare throw assertion. This guard throws a plain Error and there is no coded envelope anywhere in packages/metadata/src - every migration in that directory throws new Error(...) - so there was no code/status to assert. Minting one would be a behaviour change well outside a one-deleted-line cosmetic card. The case asserts toBeInstanceOf(Error) plus the three message properties, which is strictly more than the rejects.toThrow(regex) it replaced.

    Generated by Claude Code


    Generated by Claude Code

  9. added and removed
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Aug 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:engineRetired 2026-10-22, alias of domain:server (engine surface); in-flight cards keep it until they landpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions