Skip to content

Migration guards miss the commonest case: columns added to SCHEMA with NO migration at all - #2734

Open
jaylfc wants to merge 1 commit into
devfrom
exec/tsk-hrzgip
Open

Migration guards miss the commonest case: columns added to SCHEMA with NO migration at all#2734
jaylfc wants to merge 1 commit into
devfrom
exec/tsk-hrzgip

Conversation

@jaylfc

@jaylfc jaylfc commented Sep 2, 2026

Copy link
Copy Markdown
Owner

CARD TITLE (intent, not commit subject): Migration guards miss the commonest case: columns added to SCHEMA with NO migration at all

Autonomous build of board card tsk-hrzgip.

The two existing static migration guards both pass on a change that
provably breaks every existing install: a new column added straight to
CREATE TABLE IF NOT EXISTS inside SCHEMA with no ALTER anywhere.
CREATE TABLE IF NOT EXISTS is a no-op on existing tables, so the column
is never created on upgrade and the first INSERT crashes with
"table has no column named ". Neither existing guard has a
migration entry to inspect, so both clean by vacuity; CI cannot catch
it because tests build fresh DBs (which always have the column).

Add a third static guard, scripts/check_schema_column_migrations.py,
invoked as a new step in doc-gate.yml alongside the existing two.
Mirrors check_retrofit_migrations.py: same stdlib-only approach, same
clean/exit 0 vs violation/exit 1 contract. For every store file under
tinyagentos/ it flags a column present in a CREATE TABLE inside SCHEMA
which has NO matching ALTER TABLE ADD [COLUMN] in the same
file AND which is newly added relative to git show origin/dev:
(the third condition keeps it from flagging every column in the repo).

Proof the new guard goes RED on the exact case from the task:

$ git fetch origin pull/2416/head:pr2416
$ git checkout pr2416 -- tinyagentos/auth_requests_store.py
$ python3 scripts/check_schema_column_migrations.py
SCHEMA-COLUMN VIOLATION: tinyagentos/auth_requests_store.py: table 'auth_requests', column 'kind' added to SCHEMA with no migration
detail: new column 'kind' in CREATE TABLE auth_requests with no ALTER TABLE auth_requests ADD COLUMN kind in this file
fix: add a guarded _post_init coroutine that ALTERs this column into place after a PRAGMA table_info check
SCHEMA-COLUMN VIOLATION: tinyagentos/auth_requests_store.py: table 'auth_requests', column 'purpose' added to SCHEMA with no migration
detail: new column 'purpose' in CREATE TABLE auth_requests with no ALTER TABLE auth_requests ADD COLUMN purpose in this file
fix: add a guarded _post_init coroutine that ALTERs this column into place after a PRAGMA table_info check
SCHEMA-COLUMN VIOLATION: tinyagentos/auth_requests_store.py: table 'auth_requests', column 'requested_by_agent' added to SCHEMA with no migration
detail: new column 'requested_by_agent' in CREATE TABLE auth_requests with no ALTER TABLE auth_requests ADD COLUMN requested_by_agent in this file
fix: add a guarded _post_init coroutine that ALTERs this column into place after a PRAGMA table_info check
SCHEMA-COLUMN VIOLATION: tinyagentos/auth_requests_store.py: table 'auth_requests', column 'requested_name' added to SCHEMA with no migration
detail: new column 'requested_name' in CREATE TABLE auth_requests with no ALTER TABLE auth_requests ADD COLUMN requested_name in this file
fix: add a guarded _post_init coroutine that ALTERs this column into place after a PRAGMA table_info check
SCHEMA-COLUMN VIOLATION: tinyagentos/auth_requests_store.py: table 'auth_requests', column 'requested_slug' added to SCHEMA with no migration
detail: new column 'requested_slug' in CREATE TABLE auth_requests with no ALTER TABLE auth_requests ADD COLUMN requested_slug in this file
fix: add a guarded _post_init coroutine that ALTERs this column into place after a PRAGMA table_info check
(exit 1)

$ git checkout HEAD -- tinyagentos/auth_requests_store.py
$ python3 scripts/check_schema_column_migrations.py
schema-column-guard: clean
(exit 0)

The migration runner's bookkeeping table (schema_migrations) is
excluded because it is created on first boot before any store init and
its columns are not subject to the upgrade-bricks-install pattern.

Adds pitfall 19 to docs/contributor-pitfalls.md describing the failure
mode and pointing at the new guard.

Tests: 12 new tests in tests/scripts/test_check_schema_column_migrations.py,
both passing and failing fixtures, plus split/inline-constraint/alter-
silences-violation coverage. Full tests/scripts/ suite: 159 passed.

Docs-Reviewed: the guard is wired into doc-gate.yml as the new
schema-column-guard step and described in the contributor-pitfalls doc
(pitfall 19), which already enumerates the two sibling guards
(pitfalls 11 and 18); the new entry closes the previously-undocumented
third variant.

Files:
.github/workflows/doc-gate.yml | 3 +
changelog.d/tsk-hrzgip-schema-column-guard.md | 2 +
docs/contributor-pitfalls.md | 10 +
scripts/check_schema_column_migrations.py | 249 +++++++++++++++++++++
.../scripts/test_check_schema_column_migrations.py | 225 +++++++++++++++++++
5 files changed, 489 insertions(+)

…rzgip)

The two existing static migration guards both pass on a change that
provably breaks every existing install: a new column added straight to
CREATE TABLE IF NOT EXISTS inside SCHEMA with no ALTER anywhere.
CREATE TABLE IF NOT EXISTS is a no-op on existing tables, so the column
is never created on upgrade and the first INSERT crashes with
"table <t> has no column named <c>". Neither existing guard has a
migration entry to inspect, so both clean by vacuity; CI cannot catch
it because tests build fresh DBs (which always have the column).

Add a third static guard, scripts/check_schema_column_migrations.py,
invoked as a new step in doc-gate.yml alongside the existing two.
Mirrors check_retrofit_migrations.py: same stdlib-only approach, same
clean/exit 0 vs violation/exit 1 contract. For every store file under
tinyagentos/ it flags a column present in a CREATE TABLE inside SCHEMA
which has NO matching ALTER TABLE <t> ADD [COLUMN] <c> in the same
file AND which is newly added relative to git show origin/dev:<path>
(the third condition keeps it from flagging every column in the repo).

Proof the new guard goes RED on the exact case from the task:

  $ git fetch origin pull/2416/head:pr2416
  $ git checkout pr2416 -- tinyagentos/auth_requests_store.py
  $ python3 scripts/check_schema_column_migrations.py
  SCHEMA-COLUMN VIOLATION: tinyagentos/auth_requests_store.py: table 'auth_requests', column 'kind' added to SCHEMA with no migration
      detail: new column 'kind' in CREATE TABLE auth_requests with no ALTER TABLE auth_requests ADD COLUMN kind in this file
      fix: add a guarded _post_init coroutine that ALTERs this column into place after a PRAGMA table_info check
  SCHEMA-COLUMN VIOLATION: tinyagentos/auth_requests_store.py: table 'auth_requests', column 'purpose' added to SCHEMA with no migration
      detail: new column 'purpose' in CREATE TABLE auth_requests with no ALTER TABLE auth_requests ADD COLUMN purpose in this file
      fix: add a guarded _post_init coroutine that ALTERs this column into place after a PRAGMA table_info check
  SCHEMA-COLUMN VIOLATION: tinyagentos/auth_requests_store.py: table 'auth_requests', column 'requested_by_agent' added to SCHEMA with no migration
      detail: new column 'requested_by_agent' in CREATE TABLE auth_requests with no ALTER TABLE auth_requests ADD COLUMN requested_by_agent in this file
      fix: add a guarded _post_init coroutine that ALTERs this column into place after a PRAGMA table_info check
  SCHEMA-COLUMN VIOLATION: tinyagentos/auth_requests_store.py: table 'auth_requests', column 'requested_name' added to SCHEMA with no migration
      detail: new column 'requested_name' in CREATE TABLE auth_requests with no ALTER TABLE auth_requests ADD COLUMN requested_name in this file
      fix: add a guarded _post_init coroutine that ALTERs this column into place after a PRAGMA table_info check
  SCHEMA-COLUMN VIOLATION: tinyagentos/auth_requests_store.py: table 'auth_requests', column 'requested_slug' added to SCHEMA with no migration
      detail: new column 'requested_slug' in CREATE TABLE auth_requests with no ALTER TABLE auth_requests ADD COLUMN requested_slug in this file
      fix: add a guarded _post_init coroutine that ALTERs this column into place after a PRAGMA table_info check
  (exit 1)

  $ git checkout HEAD -- tinyagentos/auth_requests_store.py
  $ python3 scripts/check_schema_column_migrations.py
  schema-column-guard: clean
  (exit 0)

The migration runner's bookkeeping table (schema_migrations) is
excluded because it is created on first boot before any store init and
its columns are not subject to the upgrade-bricks-install pattern.

Adds pitfall 19 to docs/contributor-pitfalls.md describing the failure
mode and pointing at the new guard.

Tests: 12 new tests in tests/scripts/test_check_schema_column_migrations.py,
both passing and failing fixtures, plus split/inline-constraint/alter-
silences-violation coverage. Full tests/scripts/ suite: 159 passed.

Docs-Reviewed: the guard is wired into doc-gate.yml as the new
schema-column-guard step and described in the contributor-pitfalls doc
(pitfall 19), which already enumerates the two sibling guards
(pitfalls 11 and 18); the new entry closes the previously-undocumented
third variant.
@qodo-code-review

Copy link
Copy Markdown

ⓘ Qodo reviews are paused because your trial has ended. Ask your workspace admin to add credits to resume reviews. Manage billing

@coderabbitai

coderabbitai Bot commented Sep 2, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 17 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: d93d90a0-4d83-49dc-a7a5-36f45c0ab0aa

📥 Commits

Reviewing files that changed from the base of the PR and between 58973b9 and 8d88431.

📒 Files selected for processing (5)
  • .github/workflows/doc-gate.yml
  • changelog.d/tsk-hrzgip-schema-column-guard.md
  • docs/contributor-pitfalls.md
  • scripts/check_schema_column_migrations.py
  • tests/scripts/test_check_schema_column_migrations.py

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gitar-bot

gitar-bot Bot commented Sep 2, 2026

Copy link
Copy Markdown

Important

You are using the Gitar free plan. Upgrade to unlock code review, CI analysis, auto-apply, custom automations, and more.

Gitar

Prints ``schema-column-guard: clean`` and exits 0 when no violations, or
prints each violation and exits 1.

Dependency-light: stdlib only (ast + re + pathlib + subprocess).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Stale dependency list in docstring.

The docstring claims the script uses ast + re + pathlib + subprocess, but ast is never imported or used. This is misleading for readers comparing it with the sibling check_retrofit_migrations.py (which actually uses ast). Either drop ast from the dependency-light line or explain why it is mentioned.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

return out


def _extract_schemas(source: str) -> list[str]:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: _extract_schemas matches any triple-quoted string containing CREATE TABLE, not specifically SCHEMA = """...""" constants.

A module/class docstring, a long comment, or an unrelated raw SQL string that happens to mention CREATE TABLE (e.g. example snippets in docstrings) will be picked up and its columns compared against origin/dev. This will produce false positives and silently fail CI on innocent docs. The sibling check_retrofit_migrations.py avoids this by using ast to resolve only SCHEMA/MIGRATIONS constant assignments — that proven pattern would be safer here.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.


origin_cols = _origin_dev_columns(path)

added_columns: set[tuple[str, str]] = set()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL: _ADD_COLUMN_RE is matched against the entire file source, including docstrings and comments, which causes false negatives.

Any ALTER TABLE <t> ADD [COLUMN] <c> mentioned in a Python docstring, # comment, log message, test fixture, or unrelated string literal anywhere in the same file will mark (t, c) as "having a migration" and silence the real violation. A contributor who writes a comment like # See ALTER TABLE auth_requests ADD COLUMN kind in PR #2416 would bypass the guard. Constrain the search to SQL inside the actual _post_init body (e.g. parse the _post_init AST node, or strip comments/strings before regexing), or at minimum ignore text inside triple-quoted and #-prefixed lines.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

return columns


def _origin_dev_columns(path: Path) -> dict[str, set[str]] | None:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Silent fallback to "treat file as new" when git show origin/dev:<path> fails masks CI misconfiguration.

_origin_dev_columns returns None on any failure (missing ref, transient network error, ref name change), and the caller then treats every column as new — i.e. every existing column on every store becomes a "SCHEMA-COLUMN VIOLATION". CI that loses its origin/dev ref (renamed branch, shallow clone, fork PR with restricted refs) would suddenly fail with thousands of violations rather than a single actionable error. Emit a loud warning and exit non-zero when the ref is missing, and only fall back to "new file" when the file genuinely does not exist on origin/dev.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

)

# ALTER TABLE <table> ADD [COLUMN] <col> ...
_ADD_COLUMN_RE = re.compile(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: _ADD_COLUMN_RE does not anchor on SQL boundaries, so it will also match strings in unrelated contexts.

The same concern as the critical finding above, but worth a separate call-out: the regex matches ALTER\s+TABLE\s+(\w+)\s+ADD\s+(?:COLUMN\s+)?(\w+) literally anywhere in the file. There is no attempt to restrict to multi-line SQL string literals or to the body of a _post_init function. Even Python identifiers like ALTER_TABLE_ADD_COLUMN_HELPER = ... (which would not match because of underscores, but AlterTable foo add bar in a name would also be filtered out only by the lack of whitespace) are not a risk — but the broader false-negative window on docstrings/comments is real and should be closed.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.



if __name__ == "__main__":
sys.exit(main()) No newline at end of file

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: Missing trailing newline at end of file.

The diff shows \ No newline at end of file. POSIX text files should end with a newline; many tools (cat, wc -l, some diff/merge drivers, several editors) misbehave on files without one. Add a final \n. Same applies to tests/scripts/test_check_schema_column_migrations.py (line 225) and changelog.d/tsk-hrzgip-schema-column-guard.md.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

)


def _split_columns(body: str) -> set[str]:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: _split_columns uses a hand-rolled paren-depth scanner; consider documenting the supported dialect edge cases or accepting the limitation.

The scanner splits on top-level commas and skips segments whose first token looks like a table-level constraint. Edge cases not handled: a column whose DEFAULT expression itself contains a top-level comma at paren-depth 0 (unusual but legal in some extensions), and a column whose name collides with one of the NON_COLUMN_KEYWORDS (e.g. a quoted column literally named select). Neither is fatal but they would silently drop or misclassify a column. A one-line comment in the function noting the limitation, or an explicit raise NotImplementedError for the rare case, would help future readers.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Sep 2, 2026

Copy link
Copy Markdown

Code Review Summary

Status: 6 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 1
WARNING 3
SUGGESTION 2
Issue Details (click to expand)

CRITICAL

File Line Issue
scripts/check_schema_column_migrations.py 200 _ADD_COLUMN_RE is matched against the entire file source (including docstrings, comments, log messages), causing false negatives where a real migration is missing but a textual mention of ALTER TABLE in a comment silences the violation.

WARNING

File Line Issue
scripts/check_schema_column_migrations.py 41 Stale ast + re + pathlib + subprocess dependency list in docstring; ast is never imported or used.
scripts/check_schema_column_migrations.py 166 _extract_schemas matches any triple-quoted string containing CREATE TABLE (docstrings, raw SQL samples), not specifically SCHEMA = """...""" constants — risk of false positives. The sibling guard uses AST to resolve only the SCHEMA constant.
scripts/check_schema_column_migrations.py 143 Silent fallback to "treat file as new" when git show origin/dev:<path> fails masks CI misconfiguration; a missing or renamed origin/dev ref would cause every column on every store to be flagged as a violation.

SUGGESTION

File Line Issue
scripts/check_schema_column_migrations.py 249 Missing trailing newline at end of file (also affects tests/scripts/test_check_schema_column_migrations.py and changelog.d/tsk-hrzgip-schema-column-guard.md).
scripts/check_schema_column_migrations.py 93 _split_columns hand-rolled scanner does not document edge cases (DEFAULT expressions with top-level commas, columns named after NON_COLUMN_KEYWORDS).
Files Reviewed (5 files)
  • .github/workflows/doc-gate.yml - 0 issues
  • changelog.d/tsk-hrzgip-schema-column-guard.md - 0 issues (missing trailing newline noted in suggestion)
  • docs/contributor-pitfalls.md - 0 issues
  • scripts/check_schema_column_migrations.py - 6 issues
  • tests/scripts/test_check_schema_column_migrations.py - 0 issues (coverage looks solid; missing trailing newline noted in suggestion)

Fix these issues in Kilo Cloud


Reviewed by minimax-m3:free · Input: 42.3K · Output: 7.2K · Cached: 287.5K

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant