Segment and RudderStack skill for Codex
One event plan, clean identity, every destination. Codex instruments apps with Segment or RudderStack from a tracking plan instead of ad hoc names, sends revenue and lifecycle events from the server with stable messageIds, and stitches anonymous visitors to users at login without merging people. Loading respects consent, server libraries flush before exit, and every new event is checked in the Source debugger and destination delivery views.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- segment-cdp Installs to ~/.agents/skills/segment-cdp/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install segment-cdp The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project segment-cdp Installs to .agents/skills/segment-cdp/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/segment-cdp.zipunzip -o segment-cdp.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) segment-cdp PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $segment-cdp, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Implements customer event pipelines with Segment or RudderStack - Analytics.js 2.0 and server libraries, the identify/track/page/group calls, Object Action event naming, anonymousId to userId stitching, consent gating, server-side events for revenue, tracking plan enforcement and destination debugging. Use when instrumenting an app with Segment or RudderStack, cleaning up inconsistent event names, fixing broken user identity or duplicate events, moving tracking server-side, or checking why data is missing in a destination.
Use it when
- Adding Segment or RudderStack tracking to a web, mobile or server app
- Event names and properties are inconsistent across the codebase
- Users are not stitched correctly between anonymous and logged-in sessions
- Data is missing or duplicated in a downstream destination
Use something else when
- You need to decide which events to track before implementing: use analytics-tracking-plan
- You send events straight to GA4 without a CDP: use ga4-analytics
How to check the result
Every new event shows in the Source debugger with the planned name and types, identity holds across login, and declining consent produces no requests to the CDP hosts.
The mistake it prevents
Calling identify with an email or a changing value as userId, which merges or splits people permanently in destinations.
Done well means: every event name and property comes from a written tracking plan, identity is stitched at login without merging different people, revenue and lifecycle events are sent from the server, nothing loads before consent where consent is required, and each event can be traced from the code to the Source debugger to the destination.
Inspect first
- Read
AGENTS.md; thenrg -n "analytics\.(track|identify|page|group)|@segment/|rudder" --glob '!node_modules'. - Export the existing event names and properties from the code (or the Segment schema view) and compare them; inconsistent casing and duplicates are usually the first finding.
- Find the consent tool (OneTrust, Cookiebot, custom banner) and which regions require opt-in.
- Check whether a tracking plan exists (
$analytics-tracking-planwrites one). Do not invent events without it; agree names with the user.
Choose where an event is sent from
| Event type | Send from | Why |
|---|---|---|
| Page views, UI clicks, client-only interactions | Browser (Analytics.js) | Only the client sees them |
| Signed Up, Order Completed, Subscription Started, Payment Failed | Server | Ad blockers and closed tabs drop client events; the server knows the truth |
| Account / company traits | Server group | Single source, avoids stale client state |
| Mobile app lifecycle | Mobile SDK (Analytics-Kotlin, Analytics-Swift, React Native) | Automatic lifecycle events |
Never send the same business event from both sides unless destinations are configured to use only one.
Naming and payloads
- Events: Object + past-tense Action in Title Case:
Product Added,Order Completed,Trial Started. Use Segment’s e-commerce and B2B SaaS spec names where they fit; destinations map them automatically. - Properties:
snake_case, typed consistently (revenuealways a number in major units,currencyISO 4217, IDs as strings). - Traits on
identifydescribe the person (email,plan,created_at); properties ontrackdescribe the action. Do not put changing user state in everytrack. - Never put secrets, passwords, full card numbers or free-text fields that may contain them into events. Treat email and phone as personal data subject to the privacy policy.
Browser: Analytics.js 2.0
// analytics.ts
import { AnalyticsBrowser } from '@segment/analytics-next';
export const analytics = new AnalyticsBrowser();
export function startAnalytics(consented: boolean) {
if (!consented) return; // gate load on consent where required
analytics.load({ writeKey: import.meta.env.VITE_SEGMENT_WRITE_KEY });
}
// usage
analytics.page();
analytics.track('Product Added', { product_id: 'sku_18342', price: 189, currency: 'EUR', quantity: 1 });
- Calls made before
loadare buffered and flushed after, so instrumenting first and loading on consent is safe. - Single-page apps: call
analytics.page()on route change, not only on first load. - RudderStack equivalent:
@rudderstack/analytics-jswithrudderAnalytics.load(WRITE_KEY, DATA_PLANE_URL); the call API (track,identify,page,group) matches Segment’s.
Server: Node and Python
import { Analytics } from '@segment/analytics-node';
const analytics = new Analytics({ writeKey: process.env.SEGMENT_WRITE_KEY! });
analytics.track({
userId: order.userId,
event: 'Order Completed',
messageId: `order-completed-${order.id}`, // stable: dedupes retries
timestamp: order.paidAt,
properties: { order_id: order.id, revenue: order.total / 100, currency: order.currency },
});
process.on('SIGTERM', async () => { await analytics.closeAndFlush(); process.exit(0); });
import segment.analytics as analytics # package: segment-analytics-python
analytics.write_key = os.environ["SEGMENT_WRITE_KEY"]
analytics.track(user_id, "Order Completed", {"order_id": oid, "revenue": 189.0, "currency": "EUR"})
analytics.flush() # in scripts, jobs and serverless handlers before exit
- Server libraries batch in memory. Serverless functions and short scripts must flush before returning or events are lost.
- Use a deterministic
messageIdfor events emitted by retried jobs or webhook handlers. - Pass the original
timestampwhen backfilling; otherwise all history lands on today. - Backfills of past data into production destinations are outward-facing and may trigger emails or ad audiences; confirm with the user and prefer a dedicated source.
Identity
- Anonymous visitors get an
anonymousIdfrom Analytics.js automatically. - On sign-up or login call
analytics.identify(user.id, { email, plan })in the browser so the anonymous history links to the user. Use the internal immutable user ID, never email, asuserId. - Server events use the same
userId. If the server must link a pre-login session, pass the browser’sanonymousId(read withanalytics.user().anonymousId()and send it with the request). - On logout call
analytics.reset()so the next person on the device starts anonymous. - B2B:
analytics.group(accountId, { name, plan, employees })after identify.
Shared devices and calling identify with the wrong ID merge two people irreversibly in many
destinations; test identity flows in a dev source first.
Consent
- Map consent categories (analytics, advertising, functional) to destinations. Segment Consent Management and RudderStack consent settings can filter destinations by category; the stricter option is not loading the library until consent.
- Record consent changes as an event (
Consent Updated) only if the tracking plan allows it. - Server-side events still need a lawful basis; check with the user’s privacy owner for regions that require opt-in.
Tracking plan enforcement
- Segment Protocols or RudderStack Tracking Plans can block or flag unplanned events and wrong types. Turn on “flag” first, review violations for a week, then “block”.
- Wrap calls in a typed helper so the compiler enforces the plan:
type Events = {
'Product Added': { product_id: string; price: number; currency: string; quantity: number };
'Order Completed': { order_id: string; revenue: number; currency: string };
};
export const trackEvent = <K extends keyof Events>(name: K, props: Events[K]) => analytics.track(name, props);
Segment Typewriter generated this from a plan in the past; a hand-written map like the above is lighter and easy to keep in sync.
Verification
- Each new event appears in the Source Debugger with the planned name, property types and the
expected
userId/anonymousId. - A login flow shows the same
anonymousIdbefore and afteridentify, then theuserId. - Declining consent results in no requests to
api.segment.io/cdn.segment.com(or the RudderStack data plane) in the network tab. - Server events from a retried job show one event per
messageIdin the destination. - For each destination, the Event Delivery view shows successes for the new events; fix mapping errors there rather than changing the source payload blindly.
Gotchas
- Ad blockers block the default CDN and API hosts; a custom domain proxy or server-side sending for key events recovers them.
revenuesent in cents in one place and in major units in another breaks every revenue report.- Calling
identifyon every page with unchanged traits wastes volume (MTU and event billing). - Device-mode destinations load third-party scripts in the browser; cloud-mode keeps them off the page.
- Renaming an event creates a new one in every destination; plan migrations with both names for a transition period.
Output
Report: events added or renamed (name, source, properties), identity flow, consent behaviour, destinations affected, and debugger evidence for each event.
What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Segment and RudderStack"
short_description: "One event plan, clean identity, every destination"
default_prompt: "$segment-cdp Instrument our signup and checkout funnel with Segment" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.