Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Plan and execute Customer.io SDK upgrades and migrations. Use when upgrading customerio-node versions, migrating from legacy APIs, or updating to new SDK patterns. Trigger: "upgrade customer.io", "customer.io migration", "update customer.io sdk", "customer.io breaking changes".
.claude/skills/jeremylongshore-customerio-upgrade-migration/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-06 | ✗→✓ | ▲ Improved | 105% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 182% | 0% |
| case-14 | ✗→✓ | ▲ Improved | 97% | 0% |
| case-19 | ✗→✓ | ▲ Improved | 12% | 0% |
| case-11 | ✓→✓ | = Same ✓ | 101% | 0% |
!npm list customerio-node 2>/dev/null | grep customerio || echo 'customerio-node: not installed' !npm view customerio-node version 2>/dev/null || echo 'Cannot check latest version'
Plan and execute customerio-node SDK upgrades safely: assess current version, review breaking changes, apply code migrations, and validate with staged rollout.
npm list customerio-node)CustomerIO to Modern TrackClient + APIClientOlder versions of customerio-node used a single CustomerIO class. Modern versions split into TrackClient (tracking) and APIClient (transactional/broadcasts).
typescript// BEFORE — Legacy pattern (customerio-node < 2.x) const CustomerIO = require("customerio-node"); const cio = new CustomerIO(siteId, apiKey); cio.identify("user-1", { email: "user@example.com" }); cio.track("user-1", { name: "event_name" }); // AFTER — Modern pattern (customerio-node >= 2.x) import { TrackClient, APIClient, RegionUS } from "customerio-node"; const cio = new TrackClient(siteId, apiKey, { region: RegionUS }); await cio.identify("user-1", { email: "user@example.com" }); await cio.track("user-1", { name: "event_name", data: {} }); const api = new APIClient(appApiKey, { region: RegionUS }); await api.sendEmail(request);
Key changes:
TrackClient replaces CustomerIO for identify/trackAPIClient is new — handles transactional + broadcastsRegionUS or RegionEU)await){ name, data } object instead of positional argstypescript// scripts/cio-version-check.ts import { readFileSync, existsSync } from "fs"; function assessVersion() { // Check installed version const lockPath = "package-lock.json"; if (existsSync(lockPath)) { const lock = JSON.parse(readFileSync(lockPath, "utf-8")); const installed = lock.packages?.["node_modules/customerio-node"]?.version ?? lock.dependencies?.["customerio-node"]?.version ?? "not found in lockfile"; console.log(`Installed: customerio-node@${installed}`); } // Check package.json declared version const pkg = JSON.parse(readFileSync("package.json", "utf-8")); const declared = pkg.dependencies?.["customerio-node"] ?? "not declared"; console.log(`Declared: ${declared}`); // Search for usage patterns console.log("\nUsage pattern check:"); console.log("- Look for 'new CustomerIO(' → legacy pattern, needs migration"); console.log("- Look for 'new TrackClient(' → modern pattern"); console.log("- Look for 'RegionUS/RegionEU' → region-aware (good)"); } assessVersion();
typescript// Common breaking changes between major versions: // v1.x → v2.x: // - CustomerIO class → TrackClient + APIClient // - Callbacks → Promises (async/await) // - Region parameter added (defaults to US) // - SendEmailRequest constructor changed // v2.x → v3.x: // - Import path may change // - TypeScript types improved // - Error object structure may change // Always check the official changelog: // https://github.com/customerio/customerio-node/blob/main/CHANGELOG.md
typescript// lib/customerio-migration.ts // Adapter that supports both old and new patterns during migration import { TrackClient, APIClient, RegionUS } from "customerio-node"; export class CioMigrationClient { private trackClient: TrackClient; private apiClient: APIClient | null; constructor(config: { siteId: string; trackApiKey: string; appApiKey?: string; region?: "us" | "eu"; }) { const region = config.region === "eu" ? (await import("customerio-node")).RegionEU : RegionUS; this.trackClient = new TrackClient(config.siteId, config.trackApiKey, { region, }); this.apiClient = config.appApiKey ? new APIClient(config.appApiKey, { region }) : null; } // Legacy-compatible identify (accepts both old and new signatures) async identify(userId: string, attrs: Record<string, any>): Promise<void> { // Ensure timestamps are in seconds, not milliseconds if (attrs.created_at && attrs.created_at > 1e12) { attrs.created_at = Math.floor(attrs.created_at / 1000); } await this.trackClient.identify(userId, attrs); } // Legacy-compatible track (normalizes data format) async track( userId: string, eventOrOpts: string | { name: string; data?: Record<string, any> }, data?: Record<string, any> ): Promise<void> { if (typeof eventOrOpts === "string") { // Legacy: track("user", "event_name", { key: "value" }) await this.trackClient.track(userId, { name: eventOrOpts, data: data ?? {}, }); } else { // Modern: track("user", { name: "event_name", data: {} }) await this.trackClient.track(userId, eventOrOpts); } } get app(): APIClient { if (!this.apiClient) { throw new Error("App API key not configured"); } return this.apiClient; } }
bash# Update to latest version npm install customerio-node@latest # Run your test suite npm test # Run integration tests against dev workspace npx dotenv -e .env.development -- npx vitest run tests/customerio
typescript// tests/cio-migration.test.ts import { describe, it, expect } from "vitest"; import { TrackClient, APIClient, RegionUS } from "customerio-node"; const cio = new TrackClient( process.env.CUSTOMERIO_SITE_ID!, process.env.CUSTOMERIO_TRACK_API_KEY!, { region: RegionUS } ); describe("Post-migration validation", () => { const testId = `migration-test-${Date.now()}`; it("identify works with new client", async () => { await expect( cio.identify(testId, { email: `${testId}@test.example.com`, created_at: Math.floor(Date.now() / 1000), }) ).resolves.not.toThrow(); }); it("track works with object format", async () => { await expect( cio.track(testId, { name: "migration_test", data: { version: "new" } }) ).resolves.not.toThrow(); }); it("suppress and destroy work", async () => { await cio.suppress(testId); await expect(cio.destroy(testId)).resolves.not.toThrow(); }); });
typescript// Use feature flag to gradually migrate traffic import { createHash } from "crypto"; function useNewSdk(userId: string, rolloutPercent: number): boolean { const hash = createHash("md5").update(`cio-migration-${userId}`).digest("hex"); return parseInt(hash.substring(0, 8), 16) % 100 < rolloutPercent; } // In your application code: if (useNewSdk(userId, 10)) { // New SDK path await newClient.identify(userId, attrs); } else { // Legacy SDK path (until migration complete) await legacyClient.identify(userId, attrs); }
| Issue | Solution | |-------|----------| | TrackClient is not a constructor | Old import style — use import { TrackClient } from "customerio-node" | | region is not defined | Import RegionUS or RegionEU from customerio-node | | Methods not returning Promises | Upgrade to latest — old versions used callbacks | | TypeError: cio.track is not a function | Using APIClient instead of TrackClient for tracking |
After successful migration, proceed to customerio-ci-integration for CI/CD setup.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-11 | pass→pass | 10,628 | 9,444 | -11% | 1 | 1 | 0% | 2,048 | 4,108 | +101% | 0 | 0 | — |
case-01 | pass→pass | 21,043 | 16,722 | -21% | 1 | 1 | 0% | 4,617 | 6,291 | +36% | 0 | 0 | — |
case-02 | fail→fail | 19,730 | 17,465 | -11% | 1 | 1 | 0% | 4,283 | 6,498 | +52% | 0 | 0 | — |
case-03 | pass→pass | 12,614 | 10,802 | -14% | 1 | 1 | 0% | 2,798 | 4,661 | +67% | 0 | 0 | — |
case-04 | pass→pass | 8,831 | 8,129 | -8% | 1 | 1 | 0% | 1,898 | 4,079 | +115% | 0 | 0 | — |
case-05 | pass→pass | 10,497 | 8,575 | -18% | 1 | 1 | 0% | 1,933 | 4,004 | +107% | 0 | 0 | — |
case-06 | fail→pass | 8,846 | 6,056 | -32% | 1 | 1 | 0% | 1,703 | 3,486 | +105% | 0 | 0 | — |
case-07 | pass→pass | 9,498 | 6,810 | -28% | 1 | 1 | 0% | 1,867 | 3,774 | +102% | 0 | 0 | — |
case-08 | pass→pass | 5,990 | 3,217 | -46% | 1 | 1 | 0% | 1,160 | 2,915 | +151% | 0 | 0 | — |
case-09 | pass→pass | 12,528 | 8,018 | -36% | 1 | 1 | 0% | 2,396 | 3,993 | +67% | 0 | 0 | — |
case-10 | pass→pass | 6,766 | 4,497 | -34% | 1 | 1 | 0% | 1,489 | 3,151 | +112% | 0 | 0 | — |
case-12 | fail→pass | 5,397 | 3,484 | -35% | 1 | 1 | 0% | 1,060 | 2,991 | +182% | 0 | 0 | — |
case-13 | pass→pass | 8,422 | 5,012 | -40% | 1 | 1 | 0% | 1,663 | 3,441 | +107% | 0 | 0 | — |
case-14 | fail→pass | 10,056 | 6,245 | -38% | 1 | 1 | 0% | 1,844 | 3,633 | +97% | 0 | 0 | — |
case-15 | pass→pass | 10,688 | 4,700 | -56% | 1 | 1 | 0% | 1,752 | 3,199 | +83% | 0 | 0 | — |
case-16 | pass→pass | 8,512 | 3,131 | -63% | 1 | 1 | 0% | 1,630 | 2,856 | +75% | 0 | 0 | — |
case-17 | pass→pass | 7,757 | 5,448 | -30% | 1 | 1 | 0% | 1,564 | 3,447 | +120% | 0 | 0 | — |
case-18 | pass→pass | 9,633 | 3,972 | -59% | 1 | 1 | 0% | 1,720 | 3,062 | +78% | 0 | 0 | — |
case-19 | fail→pass | 13,357 | 2,859 | -79% | 1 | 1 | 0% | 2,576 | 2,894 | +12% | 0 | 0 | — |
case-20 | pass→pass | 5,354 | 2,415 | -55% | 1 | 1 | 0% | 1,036 | 2,812 | +171% | 0 | 0 | — |
case-21 | pass→pass | 14,654 | 7,104 | -52% | 1 | 1 | 0% | 3,079 | 4,012 | +30% | 0 | 0 | — |
case-22 | pass→pass | 9,682 | 1,984 | -80% | 1 | 1 | 0% | 1,791 | 2,694 | +50% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +18 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.