Skip to content

fix: bootstrap campaigns.type column on startup#472

Merged
bd73-com merged 1 commit into
mainfrom
claude/fix-missing-column-type-O3uyQ
May 5, 2026
Merged

fix: bootstrap campaigns.type column on startup#472
bd73-com merged 1 commit into
mainfrom
claude/fix-missing-column-type-O3uyQ

Conversation

@bd73-com
Copy link
Copy Markdown
Owner

@bd73-com bd73-com commented May 4, 2026

Summary

The scheduled welcome campaign was crashing every run with column "c.type" does not exist, blocking the only enabled automated campaign. The campaigns.type column was added to shared/schema.ts when automated campaigns shipped, but production databases provisioned before that change never got the column — and there was no ensureXxx() migration to backfill it on boot.

This PR adds the missing migration. ensureCampaignTypeColumn() runs an idempotent ALTER TABLE campaigns ADD COLUMN IF NOT EXISTS type TEXT NOT NULL DEFAULT 'manual' on every startup, gates /api behind the existing SCHEMA_NOT_READY 503 if the migration fails, and runs before ensureCampaignPartialIndexes because the partial-index predicate (WHERE type = 'automated') also depends on the column.

Changes

server/services/ensureTables.ts

  • New ensureCampaignTypeColumn() exported function. Returns true on success, false (with console.error) on failure — same shape as the other column-bootstrap helpers (ensureMonitorHealthColumns, ensureMonitorPendingRetryColumn, ensureNotificationQueueColumns).

server/routes.ts

  • Imports the new helper.
  • Calls it after ensureMonitorChangesIndexes() and before ensureCampaignPartialIndexes().
  • Pushes "campaigns.type column" onto criticalSchemaFailures if the migration returns false, so a failed migration triggers the existing global /api → 503 SCHEMA_NOT_READY gate rather than letting the cron silently throw column "c.type" does not exist on every tick.

server/services/ensureTables.test.ts

  • Three new tests for ensureCampaignTypeColumn:
    1. Executes exactly 1 ALTER TABLE and returns true.
    2. Emits the correct DDL (ALTER TABLE campaigns, ADD COLUMN IF NOT EXISTS, type, 'manual', NOT NULL).
    3. Returns false and logs the expected error message when ALTER TABLE rejects.

server/routes.conditions.test.ts

  • Adds ensureCampaignTypeColumn: vi.fn().mockResolvedValue(true) to the ./services/ensureTables mock so the routes module under test can be loaded without unmocked exports throwing.

How to test

  1. Reproduce on a stale schema (skip if you trust the unit tests):

    -- in a scratch DB
    ALTER TABLE campaigns DROP COLUMN type;

    Then boot the server. With main as it stands, the next welcome cron tick logs error: column c.type does not exist and the campaign aborts. With this PR, startup runs the ALTER, the column is restored with default 'manual', and the cron run completes (or returns { skipped: true } if no recipients).

  2. Verify idempotency: Boot the server twice in a row against a DB that already has the column. The ALTER TABLE ... ADD COLUMN IF NOT EXISTS is a no-op the second time and emits no warnings.

  3. Verify the failure gate: Temporarily simulate a permission error (e.g. revoke ALTER privileges from the connection role). Boot the server. /api/* should return 503 { code: "SCHEMA_NOT_READY", message: "Schema not ready: …, campaigns.type column" }.

  4. Run the suite:

    npm run check
    npm run test

    All 2485 tests pass locally, including the 3 new ensureCampaignTypeColumn cases and the existing partial-index-invariants parity tests.

  5. Confirm ordering: ensureCampaignTypeColumn is called before ensureCampaignPartialIndexes in server/routes.ts:120-124. If you swap the order back, the partial-index CREATE INDEX … WHERE type = 'automated' will fail on stale schemas (silently, because that helper only console.warns on error).


Generated by Claude Code

Summary by CodeRabbit

  • New Features

    • Added automatic database schema validation for campaign type field. If the schema is not properly initialized, the API returns a service unavailable response until the server restarts.
  • Tests

    • Added test coverage for campaign type schema validation logic.

The welcome campaign cron's resolveRecipients anti-join references
`c.type = 'automated'` but pre-existing campaigns tables (created before
the type column landed) hit `column "type" does not exist` and the
scheduled run aborts. Add ensureCampaignTypeColumn that runs the
idempotent ALTER TABLE on startup, gate /api with SCHEMA_NOT_READY when
the migration fails, and run it before ensureCampaignPartialIndexes
since the partial index predicate also depends on the column.
@github-actions github-actions Bot added the fix label May 4, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 4, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: fbd6bd03-c529-40d0-b396-28351290d312

📥 Commits

Reviewing files that changed from the base of the PR and between 5cee7ed and a591f07.

📒 Files selected for processing (4)
  • server/routes.conditions.test.ts
  • server/routes.ts
  • server/services/ensureTables.test.ts
  • server/services/ensureTables.ts

📝 Walkthrough

Walkthrough

This PR introduces ensureCampaignTypeColumn(), a database migration function that creates the campaigns.type column (TEXT, NOT NULL, default 'manual') during server startup schema verification. The function is wired into route registration to block all API endpoints with a 503 SCHEMA_NOT_READY response until the column exists.

Changes

Campaign Type Column Migration

Layer / File(s) Summary
Core Migration
server/services/ensureTables.ts
New exported function ensureCampaignTypeColumn() executes ALTER TABLE campaigns ADD COLUMN IF NOT EXISTS type TEXT NOT NULL DEFAULT 'manual', returns true on success, logs and returns false on failure.
Startup Integration
server/routes.ts
Imports ensureCampaignTypeColumn() and invokes it during registerRoutes() schema readiness checks; adds "campaigns.type column" to criticalSchemaFailures if the check fails, triggering the existing global 503 gate.
Test Coverage & Mocking
server/services/ensureTables.test.ts, server/routes.conditions.test.ts
New test suite validates successful DDL execution, idempotent column creation, and error logging; mock in routes test extends ./services/ensureTables to resolve ensureCampaignTypeColumn as true.

Sequence Diagram

sequenceDiagram
    participant Server as Server Startup
    participant Routes as registerRoutes()
    participant EnsureFn as ensureCampaignTypeColumn()
    participant DB as Database

    Server->>Routes: Initialize routes
    Routes->>EnsureFn: Call ensureCampaignTypeColumn()
    EnsureFn->>DB: ALTER TABLE campaigns ADD COLUMN IF NOT EXISTS type...
    alt Column creation succeeds
        DB-->>EnsureFn: Success
        EnsureFn-->>Routes: Return true
        Routes->>Routes: Column ready ✓
        Routes-->>Server: Routes registered
    else Column creation fails
        DB-->>EnsureFn: Error
        EnsureFn->>EnsureFn: Log error, return false
        EnsureFn-->>Routes: Return false
        Routes->>Routes: Add "campaigns.type column" to criticalSchemaFailures
        Routes-->>Server: All /api/* return 503 SCHEMA_NOT_READY
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

fix

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and specifically describes the main change: adding the campaigns.type column bootstrap logic on startup to fix a production issue.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/fix-missing-column-type-O3uyQ

Review rate limit: 4/5 reviews remaining, refill in 12 minutes.

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

@bd73-com bd73-com merged commit 6be4d13 into main May 5, 2026
2 checks passed
@github-actions github-actions Bot deleted the claude/fix-missing-column-type-O3uyQ branch May 5, 2026 00:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants