[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6CCD5HR4GEJFA673GXDDDC7":3,"skill-same:01M6CCD5HR4GEJFA673GXDDDC7":67},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":31,"linkOnly":32,"markdownContent":33,"markdownBody":34,"frontMatter":35,"refName":40,"commitSha":41,"skillDirectory":42,"forksCount":14,"repositoryHtmlUrl":43,"repositoryHomepageUrl":44,"ownerHtmlUrl":45,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":46,"install":47,"resources":60},"01M6CCD5HR4GEJFA673GXDDDC7","convex-migrations",null,"Schema migration strategies for evolving applications including adding new fields, backfilling data, removing deprecated fields, index migrations, and zero-downtime migration patterns","baisharg\u002FASTN","baisharg","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F204916775?v=4","01M6CC8XHZYVK2DR22FZ1BESCG","BAISH","01M6CC8XP400QZ9DXAWTC8EG3X",2,"AGPL-3.0","https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fconvex-migrations\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27,28,29,30],"convex","convex migrations","schema migration","database","index","ZeRO","data","applications","schema","migration","migrations","data-modeling","2026-11-10 10:12:30",false,"---\nname: convex-migrations\ndisplayName: Convex Migrations\ndescription: Schema migration strategies for evolving applications including adding new fields, backfilling data, removing deprecated fields, index migrations, and zero-downtime migration patterns\nversion: 1.0.0\nauthor: Convex\ntags: [convex, migrations, schema, database, data-modeling]\n---\n\n# Convex Migrations\n\nEvolve your Convex database schema safely with patterns for adding fields, backfilling data, removing deprecated fields, and maintaining zero-downtime deployments.\n\n## Documentation Sources\n\nBefore implementing, do not assume; fetch the latest documentation:\n\n- Primary: https:\u002F\u002Fdocs.convex.dev\u002Fdatabase\u002Fschemas\n- Schema Overview: https:\u002F\u002Fdocs.convex.dev\u002Fdatabase\n- Migration Patterns: https:\u002F\u002Fstack.convex.dev\u002Fmigrate-data-postgres-to-convex\n- For broader context: https:\u002F\u002Fdocs.convex.dev\u002Fllms.txt\n\n## Instructions\n\n### Migration Philosophy\n\nConvex handles schema evolution differently than traditional databases:\n\n- No explicit migration files or commands\n- Schema changes deploy instantly with `npx convex dev`\n- Existing data is not automatically transformed\n- Use optional fields and backfill mutations for safe migrations\n\n### Adding New Fields\n\nStart with optional fields, then backfill:\n\n```typescript\n\u002F\u002F Step 1: Add optional field to schema\n\u002F\u002F convex\u002Fschema.ts\nimport { defineSchema, defineTable } from 'convex\u002Fserver'\nimport { v } from 'convex\u002Fvalues'\n\nexport default defineSchema({\n  users: defineTable({\n    name: v.string(),\n    email: v.string(),\n    \u002F\u002F New field - start as optional\n    avatarUrl: v.optional(v.string()),\n  }),\n})\n```\n\n```typescript\n\u002F\u002F Step 2: Update code to handle both cases\n\u002F\u002F convex\u002Fusers.ts\nimport { query } from '.\u002F_generated\u002Fserver'\nimport { v } from 'convex\u002Fvalues'\n\nexport const getUser = query({\n  args: { userId: v.id('users') },\n  returns: v.union(\n    v.object({\n      _id: v.id('users'),\n      name: v.string(),\n      email: v.string(),\n      avatarUrl: v.union(v.string(), v.null()),\n    }),\n    v.null(),\n  ),\n  handler: async (ctx, args) => {\n    const user = await ctx.db.get(args.userId)\n    if (!user) return null\n\n    return {\n      _id: user._id,\n      name: user.name,\n      email: user.email,\n      \u002F\u002F Handle missing field gracefully\n      avatarUrl: user.avatarUrl ?? null,\n    }\n  },\n})\n```\n\n```typescript\n\u002F\u002F Step 3: Backfill existing documents\n\u002F\u002F convex\u002Fmigrations.ts\nimport { internalMutation } from '.\u002F_generated\u002Fserver'\nimport { internal } from '.\u002F_generated\u002Fapi'\nimport { v } from 'convex\u002Fvalues'\n\nconst BATCH_SIZE = 100\n\nexport const backfillAvatarUrl = internalMutation({\n  args: {\n    cursor: v.optional(v.string()),\n  },\n  returns: v.object({\n    processed: v.number(),\n    hasMore: v.boolean(),\n  }),\n  handler: async (ctx, args) => {\n    const result = await ctx.db\n      .query('users')\n      .paginate({ numItems: BATCH_SIZE, cursor: args.cursor ?? null })\n\n    let processed = 0\n    for (const user of result.page) {\n      \u002F\u002F Only update if field is missing\n      if (user.avatarUrl === undefined) {\n        await ctx.db.patch(user._id, {\n          avatarUrl: generateDefaultAvatar(user.name),\n        })\n        processed++\n      }\n    }\n\n    \u002F\u002F Schedule next batch if needed\n    if (!result.isDone) {\n      await ctx.scheduler.runAfter(0, internal.migrations.backfillAvatarUrl, {\n        cursor: result.continueCursor,\n      })\n    }\n\n    return {\n      processed,\n      hasMore: !result.isDone,\n    }\n  },\n})\n\nfunction generateDefaultAvatar(name: string): string {\n  return `https:\u002F\u002Fapi.dicebear.com\u002F7.x\u002Finitials\u002Fsvg?seed=${encodeURIComponent(name)}`\n}\n```\n\n```typescript\n\u002F\u002F Step 4: After backfill completes, make field required\n\u002F\u002F convex\u002Fschema.ts\nexport default defineSchema({\n  users: defineTable({\n    name: v.string(),\n    email: v.string(),\n    avatarUrl: v.string(), \u002F\u002F Now required\n  }),\n})\n```\n\n### Removing Fields\n\nRemove field usage before removing from schema:\n\n```typescript\n\u002F\u002F Step 1: Stop using the field in queries and mutations\n\u002F\u002F Mark as deprecated in code comments\n\n\u002F\u002F Step 2: Remove field from schema (make optional first if needed)\n\u002F\u002F convex\u002Fschema.ts\nexport default defineSchema({\n  posts: defineTable({\n    title: v.string(),\n    content: v.string(),\n    authorId: v.id('users'),\n    \u002F\u002F legacyField: v.optional(v.string()), \u002F\u002F Remove this line\n  }),\n})\n\n\u002F\u002F Step 3: Optionally clean up existing data\n\u002F\u002F convex\u002Fmigrations.ts\nexport const removeDeprecatedField = internalMutation({\n  args: {\n    cursor: v.optional(v.string()),\n  },\n  returns: v.null(),\n  handler: async (ctx, args) => {\n    const result = await ctx.db\n      .query('posts')\n      .paginate({ numItems: 100, cursor: args.cursor ?? null })\n\n    for (const post of result.page) {\n      \u002F\u002F Use replace to remove the field entirely\n      const { legacyField, ...rest } = post as typeof post & {\n        legacyField?: string\n      }\n      if (legacyField !== undefined) {\n        await ctx.db.replace(post._id, rest)\n      }\n    }\n\n    if (!result.isDone) {\n      await ctx.scheduler.runAfter(\n        0,\n        internal.migrations.removeDeprecatedField,\n        {\n          cursor: result.continueCursor,\n        },\n      )\n    }\n\n    return null\n  },\n})\n```\n\n### Renaming Fields\n\nRenaming requires copying data to new field, then removing old:\n\n```typescript\n\u002F\u002F Step 1: Add new field as optional\n\u002F\u002F convex\u002Fschema.ts\nexport default defineSchema({\n  users: defineTable({\n    userName: v.string(), \u002F\u002F Old field\n    displayName: v.optional(v.string()), \u002F\u002F New field\n  }),\n})\n\n\u002F\u002F Step 2: Update code to read from new field with fallback\nexport const getUser = query({\n  args: { userId: v.id('users') },\n  returns: v.object({\n    _id: v.id('users'),\n    displayName: v.string(),\n  }),\n  handler: async (ctx, args) => {\n    const user = await ctx.db.get(args.userId)\n    if (!user) throw new Error('User not found')\n\n    return {\n      _id: user._id,\n      \u002F\u002F Read new field, fall back to old\n      displayName: user.displayName ?? user.userName,\n    }\n  },\n})\n\n\u002F\u002F Step 3: Backfill to copy data\nexport const backfillDisplayName = internalMutation({\n  args: { cursor: v.optional(v.string()) },\n  returns: v.null(),\n  handler: async (ctx, args) => {\n    const result = await ctx.db\n      .query('users')\n      .paginate({ numItems: 100, cursor: args.cursor ?? null })\n\n    for (const user of result.page) {\n      if (user.displayName === undefined) {\n        await ctx.db.patch(user._id, {\n          displayName: user.userName,\n        })\n      }\n    }\n\n    if (!result.isDone) {\n      await ctx.scheduler.runAfter(0, internal.migrations.backfillDisplayName, {\n        cursor: result.continueCursor,\n      })\n    }\n\n    return null\n  },\n})\n\n\u002F\u002F Step 4: After backfill, update schema to make new field required\n\u002F\u002F and remove old field\nexport default defineSchema({\n  users: defineTable({\n    \u002F\u002F userName removed\n    displayName: v.string(),\n  }),\n})\n```\n\n### Adding Indexes\n\nAdd indexes before using them in queries:\n\n```typescript\n\u002F\u002F Step 1: Add index to schema\n\u002F\u002F convex\u002Fschema.ts\nexport default defineSchema({\n  posts: defineTable({\n    title: v.string(),\n    authorId: v.id('users'),\n    publishedAt: v.optional(v.number()),\n    status: v.string(),\n  })\n    .index('by_author', ['authorId'])\n    \u002F\u002F New index\n    .index('by_status_and_published', ['status', 'publishedAt']),\n})\n\n\u002F\u002F Step 2: Deploy schema change\n\u002F\u002F Run: npx convex dev\n\n\u002F\u002F Step 3: Now use the index in queries\nexport const getPublishedPosts = query({\n  args: {},\n  returns: v.array(\n    v.object({\n      _id: v.id('posts'),\n      title: v.string(),\n      publishedAt: v.number(),\n    }),\n  ),\n  handler: async (ctx) => {\n    const posts = await ctx.db\n      .query('posts')\n      .withIndex('by_status_and_published', (q) => q.eq('status', 'published'))\n      .order('desc')\n      .take(10)\n\n    return posts\n      .filter((p) => p.publishedAt !== undefined)\n      .map((p) => ({\n        _id: p._id,\n        title: p.title,\n        publishedAt: p.publishedAt!,\n      }))\n  },\n})\n```\n\n### Changing Field Types\n\nType changes require careful migration:\n\n```typescript\n\u002F\u002F Example: Change from string to number for a \"priority\" field\n\n\u002F\u002F Step 1: Add new field with new type\n\u002F\u002F convex\u002Fschema.ts\nexport default defineSchema({\n  tasks: defineTable({\n    title: v.string(),\n    priority: v.string(), \u002F\u002F Old: \"low\", \"medium\", \"high\"\n    priorityLevel: v.optional(v.number()), \u002F\u002F New: 1, 2, 3\n  }),\n})\n\n\u002F\u002F Step 2: Backfill with type conversion\nexport const migratePriorityToNumber = internalMutation({\n  args: { cursor: v.optional(v.string()) },\n  returns: v.null(),\n  handler: async (ctx, args) => {\n    const result = await ctx.db\n      .query('tasks')\n      .paginate({ numItems: 100, cursor: args.cursor ?? null })\n\n    const priorityMap: Record\u003Cstring, number> = {\n      low: 1,\n      medium: 2,\n      high: 3,\n    }\n\n    for (const task of result.page) {\n      if (task.priorityLevel === undefined) {\n        await ctx.db.patch(task._id, {\n          priorityLevel: priorityMap[task.priority] ?? 1,\n        })\n      }\n    }\n\n    if (!result.isDone) {\n      await ctx.scheduler.runAfter(\n        0,\n        internal.migrations.migratePriorityToNumber,\n        {\n          cursor: result.continueCursor,\n        },\n      )\n    }\n\n    return null\n  },\n})\n\n\u002F\u002F Step 3: Update code to use new field\nexport const getTask = query({\n  args: { taskId: v.id('tasks') },\n  returns: v.object({\n    _id: v.id('tasks'),\n    title: v.string(),\n    priorityLevel: v.number(),\n  }),\n  handler: async (ctx, args) => {\n    const task = await ctx.db.get(args.taskId)\n    if (!task) throw new Error('Task not found')\n\n    const priorityMap: Record\u003Cstring, number> = {\n      low: 1,\n      medium: 2,\n      high: 3,\n    }\n\n    return {\n      _id: task._id,\n      title: task.title,\n      priorityLevel: task.priorityLevel ?? priorityMap[task.priority] ?? 1,\n    }\n  },\n})\n\n\u002F\u002F Step 4: After backfill, update schema\nexport default defineSchema({\n  tasks: defineTable({\n    title: v.string(),\n    \u002F\u002F priority field removed\n    priorityLevel: v.number(),\n  }),\n})\n```\n\n### Migration Runner Pattern\n\nCreate a reusable migration system:\n\n```typescript\n\u002F\u002F convex\u002Fschema.ts\nimport { defineSchema, defineTable } from 'convex\u002Fserver'\nimport { v } from 'convex\u002Fvalues'\n\nexport default defineSchema({\n  migrations: defineTable({\n    name: v.string(),\n    startedAt: v.number(),\n    completedAt: v.optional(v.number()),\n    status: v.union(\n      v.literal('running'),\n      v.literal('completed'),\n      v.literal('failed'),\n    ),\n    error: v.optional(v.string()),\n    processed: v.number(),\n  }).index('by_name', ['name']),\n\n  \u002F\u002F Your other tables...\n})\n```\n\n```typescript\n\u002F\u002F convex\u002Fmigrations.ts\nimport { internalMutation, internalQuery } from '.\u002F_generated\u002Fserver'\nimport { internal } from '.\u002F_generated\u002Fapi'\nimport { v } from 'convex\u002Fvalues'\n\n\u002F\u002F Check if migration has run\nexport const hasMigrationRun = internalQuery({\n  args: { name: v.string() },\n  returns: v.boolean(),\n  handler: async (ctx, args) => {\n    const migration = await ctx.db\n      .query('migrations')\n      .withIndex('by_name', (q) => q.eq('name', args.name))\n      .first()\n    return migration?.status === 'completed'\n  },\n})\n\n\u002F\u002F Start a migration\nexport const startMigration = internalMutation({\n  args: { name: v.string() },\n  returns: v.id('migrations'),\n  handler: async (ctx, args) => {\n    \u002F\u002F Check if already exists\n    const existing = await ctx.db\n      .query('migrations')\n      .withIndex('by_name', (q) => q.eq('name', args.name))\n      .first()\n\n    if (existing) {\n      if (existing.status === 'completed') {\n        throw new Error(`Migration ${args.name} already completed`)\n      }\n      if (existing.status === 'running') {\n        throw new Error(`Migration ${args.name} already running`)\n      }\n      \u002F\u002F Reset failed migration\n      await ctx.db.patch(existing._id, {\n        status: 'running',\n        startedAt: Date.now(),\n        error: undefined,\n        processed: 0,\n      })\n      return existing._id\n    }\n\n    return await ctx.db.insert('migrations', {\n      name: args.name,\n      startedAt: Date.now(),\n      status: 'running',\n      processed: 0,\n    })\n  },\n})\n\n\u002F\u002F Update migration progress\nexport const updateMigrationProgress = internalMutation({\n  args: {\n    migrationId: v.id('migrations'),\n    processed: v.number(),\n  },\n  returns: v.null(),\n  handler: async (ctx, args) => {\n    const migration = await ctx.db.get(args.migrationId)\n    if (!migration) return null\n\n    await ctx.db.patch(args.migrationId, {\n      processed: migration.processed + args.processed,\n    })\n\n    return null\n  },\n})\n\n\u002F\u002F Complete a migration\nexport const completeMigration = internalMutation({\n  args: { migrationId: v.id('migrations') },\n  returns: v.null(),\n  handler: async (ctx, args) => {\n    await ctx.db.patch(args.migrationId, {\n      status: 'completed',\n      completedAt: Date.now(),\n    })\n    return null\n  },\n})\n\n\u002F\u002F Fail a migration\nexport const failMigration = internalMutation({\n  args: {\n    migrationId: v.id('migrations'),\n    error: v.string(),\n  },\n  returns: v.null(),\n  handler: async (ctx, args) => {\n    await ctx.db.patch(args.migrationId, {\n      status: 'failed',\n      error: args.error,\n    })\n    return null\n  },\n})\n```\n\n```typescript\n\u002F\u002F convex\u002Fmigrations\u002FaddUserTimestamps.ts\nimport { internalMutation } from '..\u002F_generated\u002Fserver'\nimport { internal } from '..\u002F_generated\u002Fapi'\nimport { v } from 'convex\u002Fvalues'\n\nconst MIGRATION_NAME = 'add_user_timestamps_v1'\nconst BATCH_SIZE = 100\n\nexport const run = internalMutation({\n  args: {\n    migrationId: v.optional(v.id('migrations')),\n    cursor: v.optional(v.string()),\n  },\n  returns: v.null(),\n  handler: async (ctx, args) => {\n    \u002F\u002F Initialize migration on first run\n    let migrationId = args.migrationId\n    if (!migrationId) {\n      const hasRun = await ctx.runQuery(internal.migrations.hasMigrationRun, {\n        name: MIGRATION_NAME,\n      })\n      if (hasRun) {\n        console.log(`Migration ${MIGRATION_NAME} already completed`)\n        return null\n      }\n      migrationId = await ctx.runMutation(internal.migrations.startMigration, {\n        name: MIGRATION_NAME,\n      })\n    }\n\n    try {\n      const result = await ctx.db\n        .query('users')\n        .paginate({ numItems: BATCH_SIZE, cursor: args.cursor ?? null })\n\n      let processed = 0\n      for (const user of result.page) {\n        if (user.createdAt === undefined) {\n          await ctx.db.patch(user._id, {\n            createdAt: user._creationTime,\n            updatedAt: user._creationTime,\n          })\n          processed++\n        }\n      }\n\n      \u002F\u002F Update progress\n      await ctx.runMutation(internal.migrations.updateMigrationProgress, {\n        migrationId,\n        processed,\n      })\n\n      \u002F\u002F Continue or complete\n      if (!result.isDone) {\n        await ctx.scheduler.runAfter(\n          0,\n          internal.migrations.addUserTimestamps.run,\n          {\n            migrationId,\n            cursor: result.continueCursor,\n          },\n        )\n      } else {\n        await ctx.runMutation(internal.migrations.completeMigration, {\n          migrationId,\n        })\n        console.log(`Migration ${MIGRATION_NAME} completed`)\n      }\n    } catch (error) {\n      await ctx.runMutation(internal.migrations.failMigration, {\n        migrationId,\n        error: String(error),\n      })\n      throw error\n    }\n\n    return null\n  },\n})\n```\n\n## Examples\n\n### Schema with Migration Support\n\n```typescript\n\u002F\u002F convex\u002Fschema.ts\nimport { defineSchema, defineTable } from 'convex\u002Fserver'\nimport { v } from 'convex\u002Fvalues'\n\nexport default defineSchema({\n  \u002F\u002F Migration tracking\n  migrations: defineTable({\n    name: v.string(),\n    startedAt: v.number(),\n    completedAt: v.optional(v.number()),\n    status: v.union(\n      v.literal('running'),\n      v.literal('completed'),\n      v.literal('failed'),\n    ),\n    error: v.optional(v.string()),\n    processed: v.number(),\n  }).index('by_name', ['name']),\n\n  \u002F\u002F Users table with evolved schema\n  users: defineTable({\n    \u002F\u002F Original fields\n    name: v.string(),\n    email: v.string(),\n\n    \u002F\u002F Added in migration v1\n    createdAt: v.optional(v.number()),\n    updatedAt: v.optional(v.number()),\n\n    \u002F\u002F Added in migration v2\n    avatarUrl: v.optional(v.string()),\n\n    \u002F\u002F Added in migration v3\n    settings: v.optional(\n      v.object({\n        theme: v.string(),\n        notifications: v.boolean(),\n      }),\n    ),\n  })\n    .index('by_email', ['email'])\n    .index('by_createdAt', ['createdAt']),\n\n  \u002F\u002F Posts table with indexes for common queries\n  posts: defineTable({\n    title: v.string(),\n    content: v.string(),\n    authorId: v.id('users'),\n    status: v.union(\n      v.literal('draft'),\n      v.literal('published'),\n      v.literal('archived'),\n    ),\n    publishedAt: v.optional(v.number()),\n    createdAt: v.number(),\n    updatedAt: v.number(),\n  })\n    .index('by_author', ['authorId'])\n    .index('by_status', ['status'])\n    .index('by_author_and_status', ['authorId', 'status'])\n    .index('by_publishedAt', ['publishedAt']),\n})\n```\n\n## Best Practices\n\n- Never run `npx convex deploy` unless explicitly instructed\n- Never run any git commands unless explicitly instructed\n- Always start with optional fields when adding new data\n- Backfill data in batches to avoid timeouts\n- Test migrations on development before production\n- Keep track of completed migrations to avoid re-running\n- Update code to handle both old and new data during transition\n- Remove deprecated fields only after all code stops using them\n- Use pagination for large datasets\n- Add appropriate indexes before running queries on new fields\n\n## Common Pitfalls\n\n1. **Making new fields required immediately** - Breaks existing documents\n2. **Not handling undefined values** - Causes runtime errors\n3. **Large batch sizes** - Causes function timeouts\n4. **Forgetting to update indexes** - Queries fail or perform poorly\n5. **Running migrations without tracking** - May run multiple times\n6. **Removing fields before code update** - Breaks existing functionality\n7. **Not testing on development** - Production data issues\n\n## References\n\n- Convex Documentation: https:\u002F\u002Fdocs.convex.dev\u002F\n- Convex LLMs.txt: https:\u002F\u002Fdocs.convex.dev\u002Fllms.txt\n- Schemas: https:\u002F\u002Fdocs.convex.dev\u002Fdatabase\u002Fschemas\n- Database Overview: https:\u002F\u002Fdocs.convex.dev\u002Fdatabase\n- Migration Patterns: https:\u002F\u002Fstack.convex.dev\u002Fmigrate-data-postgres-to-convex\n","# Convex Migrations\n\nEvolve your Convex database schema safely with patterns for adding fields, backfilling data, removing deprecated fields, and maintaining zero-downtime deployments.\n\n## Documentation Sources\n\nBefore implementing, do not assume; fetch the latest documentation:\n\n- Primary: https:\u002F\u002Fdocs.convex.dev\u002Fdatabase\u002Fschemas\n- Schema Overview: https:\u002F\u002Fdocs.convex.dev\u002Fdatabase\n- Migration Patterns: https:\u002F\u002Fstack.convex.dev\u002Fmigrate-data-postgres-to-convex\n- For broader context: https:\u002F\u002Fdocs.convex.dev\u002Fllms.txt\n\n## Instructions\n\n### Migration Philosophy\n\nConvex handles schema evolution differently than traditional databases:\n\n- No explicit migration files or commands\n- Schema changes deploy instantly with `npx convex dev`\n- Existing data is not automatically transformed\n- Use optional fields and backfill mutations for safe migrations\n\n### Adding New Fields\n\nStart with optional fields, then backfill:\n\n```typescript\n\u002F\u002F Step 1: Add optional field to schema\n\u002F\u002F convex\u002Fschema.ts\nimport { defineSchema, defineTable } from 'convex\u002Fserver'\nimport { v } from 'convex\u002Fvalues'\n\nexport default defineSchema({\n  users: defineTable({\n    name: v.string(),\n    email: v.string(),\n    \u002F\u002F New field - start as optional\n    avatarUrl: v.optional(v.string()),\n  }),\n})\n```\n\n```typescript\n\u002F\u002F Step 2: Update code to handle both cases\n\u002F\u002F convex\u002Fusers.ts\nimport { query } from '.\u002F_generated\u002Fserver'\nimport { v } from 'convex\u002Fvalues'\n\nexport const getUser = query({\n  args: { userId: v.id('users') },\n  returns: v.union(\n    v.object({\n      _id: v.id('users'),\n      name: v.string(),\n      email: v.string(),\n      avatarUrl: v.union(v.string(), v.null()),\n    }),\n    v.null(),\n  ),\n  handler: async (ctx, args) => {\n    const user = await ctx.db.get(args.userId)\n    if (!user) return null\n\n    return {\n      _id: user._id,\n      name: user.name,\n      email: user.email,\n      \u002F\u002F Handle missing field gracefully\n      avatarUrl: user.avatarUrl ?? null,\n    }\n  },\n})\n```\n\n```typescript\n\u002F\u002F Step 3: Backfill existing documents\n\u002F\u002F convex\u002Fmigrations.ts\nimport { internalMutation } from '.\u002F_generated\u002Fserver'\nimport { internal } from '.\u002F_generated\u002Fapi'\nimport { v } from 'convex\u002Fvalues'\n\nconst BATCH_SIZE = 100\n\nexport const backfillAvatarUrl = internalMutation({\n  args: {\n    cursor: v.optional(v.string()),\n  },\n  returns: v.object({\n    processed: v.number(),\n    hasMore: v.boolean(),\n  }),\n  handler: async (ctx, args) => {\n    const result = await ctx.db\n      .query('users')\n      .paginate({ numItems: BATCH_SIZE, cursor: args.cursor ?? null })\n\n    let processed = 0\n    for (const user of result.page) {\n      \u002F\u002F Only update if field is missing\n      if (user.avatarUrl === undefined) {\n        await ctx.db.patch(user._id, {\n          avatarUrl: generateDefaultAvatar(user.name),\n        })\n        processed++\n      }\n    }\n\n    \u002F\u002F Schedule next batch if needed\n    if (!result.isDone) {\n      await ctx.scheduler.runAfter(0, internal.migrations.backfillAvatarUrl, {\n        cursor: result.continueCursor,\n      })\n    }\n\n    return {\n      processed,\n      hasMore: !result.isDone,\n    }\n  },\n})\n\nfunction generateDefaultAvatar(name: string): string {\n  return `https:\u002F\u002Fapi.dicebear.com\u002F7.x\u002Finitials\u002Fsvg?seed=${encodeURIComponent(name)}`\n}\n```\n\n```typescript\n\u002F\u002F Step 4: After backfill completes, make field required\n\u002F\u002F convex\u002Fschema.ts\nexport default defineSchema({\n  users: defineTable({\n    name: v.string(),\n    email: v.string(),\n    avatarUrl: v.string(), \u002F\u002F Now required\n  }),\n})\n```\n\n### Removing Fields\n\nRemove field usage before removing from schema:\n\n```typescript\n\u002F\u002F Step 1: Stop using the field in queries and mutations\n\u002F\u002F Mark as deprecated in code comments\n\n\u002F\u002F Step 2: Remove field from schema (make optional first if needed)\n\u002F\u002F convex\u002Fschema.ts\nexport default defineSchema({\n  posts: defineTable({\n    title: v.string(),\n    content: v.string(),\n    authorId: v.id('users'),\n    \u002F\u002F legacyField: v.optional(v.string()), \u002F\u002F Remove this line\n  }),\n})\n\n\u002F\u002F Step 3: Optionally clean up existing data\n\u002F\u002F convex\u002Fmigrations.ts\nexport const removeDeprecatedField = internalMutation({\n  args: {\n    cursor: v.optional(v.string()),\n  },\n  returns: v.null(),\n  handler: async (ctx, args) => {\n    const result = await ctx.db\n      .query('posts')\n      .paginate({ numItems: 100, cursor: args.cursor ?? null })\n\n    for (const post of result.page) {\n      \u002F\u002F Use replace to remove the field entirely\n      const { legacyField, ...rest } = post as typeof post & {\n        legacyField?: string\n      }\n      if (legacyField !== undefined) {\n        await ctx.db.replace(post._id, rest)\n      }\n    }\n\n    if (!result.isDone) {\n      await ctx.scheduler.runAfter(\n        0,\n        internal.migrations.removeDeprecatedField,\n        {\n          cursor: result.continueCursor,\n        },\n      )\n    }\n\n    return null\n  },\n})\n```\n\n### Renaming Fields\n\nRenaming requires copying data to new field, then removing old:\n\n```typescript\n\u002F\u002F Step 1: Add new field as optional\n\u002F\u002F convex\u002Fschema.ts\nexport default defineSchema({\n  users: defineTable({\n    userName: v.string(), \u002F\u002F Old field\n    displayName: v.optional(v.string()), \u002F\u002F New field\n  }),\n})\n\n\u002F\u002F Step 2: Update code to read from new field with fallback\nexport const getUser = query({\n  args: { userId: v.id('users') },\n  returns: v.object({\n    _id: v.id('users'),\n    displayName: v.string(),\n  }),\n  handler: async (ctx, args) => {\n    const user = await ctx.db.get(args.userId)\n    if (!user) throw new Error('User not found')\n\n    return {\n      _id: user._id,\n      \u002F\u002F Read new field, fall back to old\n      displayName: user.displayName ?? user.userName,\n    }\n  },\n})\n\n\u002F\u002F Step 3: Backfill to copy data\nexport const backfillDisplayName = internalMutation({\n  args: { cursor: v.optional(v.string()) },\n  returns: v.null(),\n  handler: async (ctx, args) => {\n    const result = await ctx.db\n      .query('users')\n      .paginate({ numItems: 100, cursor: args.cursor ?? null })\n\n    for (const user of result.page) {\n      if (user.displayName === undefined) {\n        await ctx.db.patch(user._id, {\n          displayName: user.userName,\n        })\n      }\n    }\n\n    if (!result.isDone) {\n      await ctx.scheduler.runAfter(0, internal.migrations.backfillDisplayName, {\n        cursor: result.continueCursor,\n      })\n    }\n\n    return null\n  },\n})\n\n\u002F\u002F Step 4: After backfill, update schema to make new field required\n\u002F\u002F and remove old field\nexport default defineSchema({\n  users: defineTable({\n    \u002F\u002F userName removed\n    displayName: v.string(),\n  }),\n})\n```\n\n### Adding Indexes\n\nAdd indexes before using them in queries:\n\n```typescript\n\u002F\u002F Step 1: Add index to schema\n\u002F\u002F convex\u002Fschema.ts\nexport default defineSchema({\n  posts: defineTable({\n    title: v.string(),\n    authorId: v.id('users'),\n    publishedAt: v.optional(v.number()),\n    status: v.string(),\n  })\n    .index('by_author', ['authorId'])\n    \u002F\u002F New index\n    .index('by_status_and_published', ['status', 'publishedAt']),\n})\n\n\u002F\u002F Step 2: Deploy schema change\n\u002F\u002F Run: npx convex dev\n\n\u002F\u002F Step 3: Now use the index in queries\nexport const getPublishedPosts = query({\n  args: {},\n  returns: v.array(\n    v.object({\n      _id: v.id('posts'),\n      title: v.string(),\n      publishedAt: v.number(),\n    }),\n  ),\n  handler: async (ctx) => {\n    const posts = await ctx.db\n      .query('posts')\n      .withIndex('by_status_and_published', (q) => q.eq('status', 'published'))\n      .order('desc')\n      .take(10)\n\n    return posts\n      .filter((p) => p.publishedAt !== undefined)\n      .map((p) => ({\n        _id: p._id,\n        title: p.title,\n        publishedAt: p.publishedAt!,\n      }))\n  },\n})\n```\n\n### Changing Field Types\n\nType changes require careful migration:\n\n```typescript\n\u002F\u002F Example: Change from string to number for a \"priority\" field\n\n\u002F\u002F Step 1: Add new field with new type\n\u002F\u002F convex\u002Fschema.ts\nexport default defineSchema({\n  tasks: defineTable({\n    title: v.string(),\n    priority: v.string(), \u002F\u002F Old: \"low\", \"medium\", \"high\"\n    priorityLevel: v.optional(v.number()), \u002F\u002F New: 1, 2, 3\n  }),\n})\n\n\u002F\u002F Step 2: Backfill with type conversion\nexport const migratePriorityToNumber = internalMutation({\n  args: { cursor: v.optional(v.string()) },\n  returns: v.null(),\n  handler: async (ctx, args) => {\n    const result = await ctx.db\n      .query('tasks')\n      .paginate({ numItems: 100, cursor: args.cursor ?? null })\n\n    const priorityMap: Record\u003Cstring, number> = {\n      low: 1,\n      medium: 2,\n      high: 3,\n    }\n\n    for (const task of result.page) {\n      if (task.priorityLevel === undefined) {\n        await ctx.db.patch(task._id, {\n          priorityLevel: priorityMap[task.priority] ?? 1,\n        })\n      }\n    }\n\n    if (!result.isDone) {\n      await ctx.scheduler.runAfter(\n        0,\n        internal.migrations.migratePriorityToNumber,\n        {\n          cursor: result.continueCursor,\n        },\n      )\n    }\n\n    return null\n  },\n})\n\n\u002F\u002F Step 3: Update code to use new field\nexport const getTask = query({\n  args: { taskId: v.id('tasks') },\n  returns: v.object({\n    _id: v.id('tasks'),\n    title: v.string(),\n    priorityLevel: v.number(),\n  }),\n  handler: async (ctx, args) => {\n    const task = await ctx.db.get(args.taskId)\n    if (!task) throw new Error('Task not found')\n\n    const priorityMap: Record\u003Cstring, number> = {\n      low: 1,\n      medium: 2,\n      high: 3,\n    }\n\n    return {\n      _id: task._id,\n      title: task.title,\n      priorityLevel: task.priorityLevel ?? priorityMap[task.priority] ?? 1,\n    }\n  },\n})\n\n\u002F\u002F Step 4: After backfill, update schema\nexport default defineSchema({\n  tasks: defineTable({\n    title: v.string(),\n    \u002F\u002F priority field removed\n    priorityLevel: v.number(),\n  }),\n})\n```\n\n### Migration Runner Pattern\n\nCreate a reusable migration system:\n\n```typescript\n\u002F\u002F convex\u002Fschema.ts\nimport { defineSchema, defineTable } from 'convex\u002Fserver'\nimport { v } from 'convex\u002Fvalues'\n\nexport default defineSchema({\n  migrations: defineTable({\n    name: v.string(),\n    startedAt: v.number(),\n    completedAt: v.optional(v.number()),\n    status: v.union(\n      v.literal('running'),\n      v.literal('completed'),\n      v.literal('failed'),\n    ),\n    error: v.optional(v.string()),\n    processed: v.number(),\n  }).index('by_name', ['name']),\n\n  \u002F\u002F Your other tables...\n})\n```\n\n```typescript\n\u002F\u002F convex\u002Fmigrations.ts\nimport { internalMutation, internalQuery } from '.\u002F_generated\u002Fserver'\nimport { internal } from '.\u002F_generated\u002Fapi'\nimport { v } from 'convex\u002Fvalues'\n\n\u002F\u002F Check if migration has run\nexport const hasMigrationRun = internalQuery({\n  args: { name: v.string() },\n  returns: v.boolean(),\n  handler: async (ctx, args) => {\n    const migration = await ctx.db\n      .query('migrations')\n      .withIndex('by_name', (q) => q.eq('name', args.name))\n      .first()\n    return migration?.status === 'completed'\n  },\n})\n\n\u002F\u002F Start a migration\nexport const startMigration = internalMutation({\n  args: { name: v.string() },\n  returns: v.id('migrations'),\n  handler: async (ctx, args) => {\n    \u002F\u002F Check if already exists\n    const existing = await ctx.db\n      .query('migrations')\n      .withIndex('by_name', (q) => q.eq('name', args.name))\n      .first()\n\n    if (existing) {\n      if (existing.status === 'completed') {\n        throw new Error(`Migration ${args.name} already completed`)\n      }\n      if (existing.status === 'running') {\n        throw new Error(`Migration ${args.name} already running`)\n      }\n      \u002F\u002F Reset failed migration\n      await ctx.db.patch(existing._id, {\n        status: 'running',\n        startedAt: Date.now(),\n        error: undefined,\n        processed: 0,\n      })\n      return existing._id\n    }\n\n    return await ctx.db.insert('migrations', {\n      name: args.name,\n      startedAt: Date.now(),\n      status: 'running',\n      processed: 0,\n    })\n  },\n})\n\n\u002F\u002F Update migration progress\nexport const updateMigrationProgress = internalMutation({\n  args: {\n    migrationId: v.id('migrations'),\n    processed: v.number(),\n  },\n  returns: v.null(),\n  handler: async (ctx, args) => {\n    const migration = await ctx.db.get(args.migrationId)\n    if (!migration) return null\n\n    await ctx.db.patch(args.migrationId, {\n      processed: migration.processed + args.processed,\n    })\n\n    return null\n  },\n})\n\n\u002F\u002F Complete a migration\nexport const completeMigration = internalMutation({\n  args: { migrationId: v.id('migrations') },\n  returns: v.null(),\n  handler: async (ctx, args) => {\n    await ctx.db.patch(args.migrationId, {\n      status: 'completed',\n      completedAt: Date.now(),\n    })\n    return null\n  },\n})\n\n\u002F\u002F Fail a migration\nexport const failMigration = internalMutation({\n  args: {\n    migrationId: v.id('migrations'),\n    error: v.string(),\n  },\n  returns: v.null(),\n  handler: async (ctx, args) => {\n    await ctx.db.patch(args.migrationId, {\n      status: 'failed',\n      error: args.error,\n    })\n    return null\n  },\n})\n```\n\n```typescript\n\u002F\u002F convex\u002Fmigrations\u002FaddUserTimestamps.ts\nimport { internalMutation } from '..\u002F_generated\u002Fserver'\nimport { internal } from '..\u002F_generated\u002Fapi'\nimport { v } from 'convex\u002Fvalues'\n\nconst MIGRATION_NAME = 'add_user_timestamps_v1'\nconst BATCH_SIZE = 100\n\nexport const run = internalMutation({\n  args: {\n    migrationId: v.optional(v.id('migrations')),\n    cursor: v.optional(v.string()),\n  },\n  returns: v.null(),\n  handler: async (ctx, args) => {\n    \u002F\u002F Initialize migration on first run\n    let migrationId = args.migrationId\n    if (!migrationId) {\n      const hasRun = await ctx.runQuery(internal.migrations.hasMigrationRun, {\n        name: MIGRATION_NAME,\n      })\n      if (hasRun) {\n        console.log(`Migration ${MIGRATION_NAME} already completed`)\n        return null\n      }\n      migrationId = await ctx.runMutation(internal.migrations.startMigration, {\n        name: MIGRATION_NAME,\n      })\n    }\n\n    try {\n      const result = await ctx.db\n        .query('users')\n        .paginate({ numItems: BATCH_SIZE, cursor: args.cursor ?? null })\n\n      let processed = 0\n      for (const user of result.page) {\n        if (user.createdAt === undefined) {\n          await ctx.db.patch(user._id, {\n            createdAt: user._creationTime,\n            updatedAt: user._creationTime,\n          })\n          processed++\n        }\n      }\n\n      \u002F\u002F Update progress\n      await ctx.runMutation(internal.migrations.updateMigrationProgress, {\n        migrationId,\n        processed,\n      })\n\n      \u002F\u002F Continue or complete\n      if (!result.isDone) {\n        await ctx.scheduler.runAfter(\n          0,\n          internal.migrations.addUserTimestamps.run,\n          {\n            migrationId,\n            cursor: result.continueCursor,\n          },\n        )\n      } else {\n        await ctx.runMutation(internal.migrations.completeMigration, {\n          migrationId,\n        })\n        console.log(`Migration ${MIGRATION_NAME} completed`)\n      }\n    } catch (error) {\n      await ctx.runMutation(internal.migrations.failMigration, {\n        migrationId,\n        error: String(error),\n      })\n      throw error\n    }\n\n    return null\n  },\n})\n```\n\n## Examples\n\n### Schema with Migration Support\n\n```typescript\n\u002F\u002F convex\u002Fschema.ts\nimport { defineSchema, defineTable } from 'convex\u002Fserver'\nimport { v } from 'convex\u002Fvalues'\n\nexport default defineSchema({\n  \u002F\u002F Migration tracking\n  migrations: defineTable({\n    name: v.string(),\n    startedAt: v.number(),\n    completedAt: v.optional(v.number()),\n    status: v.union(\n      v.literal('running'),\n      v.literal('completed'),\n      v.literal('failed'),\n    ),\n    error: v.optional(v.string()),\n    processed: v.number(),\n  }).index('by_name', ['name']),\n\n  \u002F\u002F Users table with evolved schema\n  users: defineTable({\n    \u002F\u002F Original fields\n    name: v.string(),\n    email: v.string(),\n\n    \u002F\u002F Added in migration v1\n    createdAt: v.optional(v.number()),\n    updatedAt: v.optional(v.number()),\n\n    \u002F\u002F Added in migration v2\n    avatarUrl: v.optional(v.string()),\n\n    \u002F\u002F Added in migration v3\n    settings: v.optional(\n      v.object({\n        theme: v.string(),\n        notifications: v.boolean(),\n      }),\n    ),\n  })\n    .index('by_email', ['email'])\n    .index('by_createdAt', ['createdAt']),\n\n  \u002F\u002F Posts table with indexes for common queries\n  posts: defineTable({\n    title: v.string(),\n    content: v.string(),\n    authorId: v.id('users'),\n    status: v.union(\n      v.literal('draft'),\n      v.literal('published'),\n      v.literal('archived'),\n    ),\n    publishedAt: v.optional(v.number()),\n    createdAt: v.number(),\n    updatedAt: v.number(),\n  })\n    .index('by_author', ['authorId'])\n    .index('by_status', ['status'])\n    .index('by_author_and_status', ['authorId', 'status'])\n    .index('by_publishedAt', ['publishedAt']),\n})\n```\n\n## Best Practices\n\n- Never run `npx convex deploy` unless explicitly instructed\n- Never run any git commands unless explicitly instructed\n- Always start with optional fields when adding new data\n- Backfill data in batches to avoid timeouts\n- Test migrations on development before production\n- Keep track of completed migrations to avoid re-running\n- Update code to handle both old and new data during transition\n- Remove deprecated fields only after all code stops using them\n- Use pagination for large datasets\n- Add appropriate indexes before running queries on new fields\n\n## Common Pitfalls\n\n1. **Making new fields required immediately** - Breaks existing documents\n2. **Not handling undefined values** - Causes runtime errors\n3. **Large batch sizes** - Causes function timeouts\n4. **Forgetting to update indexes** - Queries fail or perform poorly\n5. **Running migrations without tracking** - May run multiple times\n6. **Removing fields before code update** - Breaks existing functionality\n7. **Not testing on development** - Production data issues\n\n## References\n\n- Convex Documentation: https:\u002F\u002Fdocs.convex.dev\u002F\n- Convex LLMs.txt: https:\u002F\u002Fdocs.convex.dev\u002Fllms.txt\n- Schemas: https:\u002F\u002Fdocs.convex.dev\u002Fdatabase\u002Fschemas\n- Database Overview: https:\u002F\u002Fdocs.convex.dev\u002Fdatabase\n- Migration Patterns: https:\u002F\u002Fstack.convex.dev\u002Fmigrate-data-postgres-to-convex\n",{"name":5,"tags":36,"author":37,"version":38,"description":7,"displayName":39},[19,29,27,22,30],"Convex","1.0.0","Convex Migrations","main","83212e6d909b6a0fe9e93fd5a3ff1723300f5b5b",".claude\u002Fskills\u002Fconvex-migrations","https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN","https:\u002F\u002Fsafetytalent.org","https:\u002F\u002Fgithub.com\u002Fbaisharg","2026-11-13 01:54:27",{"source":8,"skillName":5,"commands":48,"prompt":58,"zipAvailable":59},[49,52,55],{"tool":50,"command":51},"npx","npx skills add baisharg\u002FASTN --skill convex-migrations",{"tool":53,"command":54},"bunx","bunx skills add baisharg\u002FASTN --skill convex-migrations",{"tool":56,"command":57},"pnpm","pnpm dlx skills add baisharg\u002FASTN --skill convex-migrations","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fconvex-migrations\u002FSKILL.md",true,[61],{"path":62,"resourceType":63,"resourceTypeDesc":64,"sizeBytes":65,"sourceHtmlUrl":66},"REFERENCE.md","REFERENCE","参考文档",6929,"https:\u002F\u002Fgithub.com\u002Fmalinskibeniamin\u002Fskills\u002Fblob\u002Fmain\u002Fdevelopment-lifecycle\u002FREFERENCE.md",[68,88,101,112,126,143],{"skillNo":69,"name":70,"nameZh":6,"description":71,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":72,"categories":73,"keywords":79,"publishedAt":31},"01M6CCE5DVER8DQWJDY51HAJF7","schema-builder","Design and generate Convex database schemas with proper validation, indexes, and relationships. Use when creating schema.ts or modifying table definitions.","https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fschema-builder\u002FSKILL.md",[74],{"categoryNo":75,"categoryName":76,"taxonomyType":77,"taxonomyTypeDesc":78},"01JBSEEDCAT000000000000015","视觉设计","SCENARIO","使用场景",[80,27,81,82,83,84,85,22,86,19,87],"schema builder","builder","indexes","validation","use","table","design","generate",{"skillNo":89,"name":90,"nameZh":6,"description":91,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":92,"categories":93,"keywords":94,"publishedAt":31},"01M6CCE36AA57CG36R4VHKFRXV","integration-tanstack-start","PostHog integration for TanStack Start full-stack applications","https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fposthog-integration-tanstack-start\u002FSKILL.md",[],[95,96,97,98,99,26,100],"integration","start","tanstack","integration tanstack start","posthog","stack",{"skillNo":102,"name":103,"nameZh":6,"description":104,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":105,"categories":106,"keywords":107,"publishedAt":31},"01M6CCE0FK6N8XAB093X7TTQ4F","migration-helper","Plan and execute Convex schema migrations safely, including adding fields, creating tables, and data transformations. Use when schema changes affect existing data.","https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fmigration-helper\u002FSKILL.md",[],[28,108,109,25,110,111,84,27,29,19],"migration helper","schema migrations safely","plan","execute",{"skillNo":113,"name":114,"nameZh":6,"description":115,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":116,"categories":117,"keywords":118,"publishedAt":31},"01M6CCDY3644G8WXXP0BDNMZPR","gsd:sprint","Autonomous phase execution with Codex validation. Runs plan\u002Fexecute\u002Fverify cycles in a bash loop until done or halted.","https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fgsd-sprint\u002FSKILL.md",[],[114,119,120,83,96,121,122,123,124,110,125,111],"resume","codex","execution","verify","Bash","loop","autonomous",{"skillNo":127,"name":128,"nameZh":6,"description":129,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":130,"categories":131,"keywords":132,"publishedAt":31},"01M6CCDVTZRTFWWGREMV6FF956","gsd:second-opinion","Get a second opinion from OpenAI Codex (GPT-5.2-codex) on code, architecture, or implementation decisions. Use when you want independent verification or an alternative perspective.","https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fgsd-second-opinion\u002FSKILL.md",[],[133,134,135,120,136,137,138,139,140,141,84,142],"gsd:second opinion","second opinion","question","architecture","code","verification","openai","gpt","decisions","implementation",{"skillNo":144,"name":145,"nameZh":6,"description":146,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":147,"categories":148,"keywords":149,"publishedAt":31},"01M6CCDSDPYYNEDERHP01N30PT","gsd:milestone-sprint","Run entire milestone autonomously with Codex validation. Auto-detects current milestone, executes all phases, and runs audit.","https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fgsd-milestone-sprint\u002FSKILL.md",[],[150,151,83,152,153,154,119,120],"gsd:milestone sprint","run","audit","interactive","auto"]