From dbbb8cacde58de21183f82ce720ba434a3b65f14 Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Tue, 1 Sep 2026 18:03:50 +0200 Subject: [PATCH 01/35] feat(agents): add the error-tracking orchestrator flow Nine prompts under context/agents/error-tracking/ backing the wizard's new `error-tracking` command. The seed establishes whether PostHog is integrated and which uploader variant applies, then queues install/init (integration-v2 step-skills, like replay-vision) on uninstrumented repos, exception capture via integration-v2-error-tracking-step, the source-map subgraph (adapted from the error-tracking-upload-source-maps flow) where the platform ships minified bundles or stripped binaries, and a report that merges it all. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01PVXmLWB31CBujD2FJ2ogvY --- .../error-tracking/capture-exceptions.md | 42 ++++++++++ context/agents/error-tracking/configure.md | 40 ++++++++++ context/agents/error-tracking/credentials.md | 50 ++++++++++++ context/agents/error-tracking/init.md | 33 ++++++++ context/agents/error-tracking/install.md | 31 ++++++++ context/agents/error-tracking/report.md | 64 +++++++++++++++ .../error-tracking/setup-error-tracking.md | 78 +++++++++++++++++++ context/agents/error-tracking/test-setup.md | 41 ++++++++++ context/agents/error-tracking/wire-ci.md | 37 +++++++++ 9 files changed, 416 insertions(+) create mode 100644 context/agents/error-tracking/capture-exceptions.md create mode 100644 context/agents/error-tracking/configure.md create mode 100644 context/agents/error-tracking/credentials.md create mode 100644 context/agents/error-tracking/init.md create mode 100644 context/agents/error-tracking/install.md create mode 100644 context/agents/error-tracking/report.md create mode 100644 context/agents/error-tracking/setup-error-tracking.md create mode 100644 context/agents/error-tracking/test-setup.md create mode 100644 context/agents/error-tracking/wire-ci.md diff --git a/context/agents/error-tracking/capture-exceptions.md b/context/agents/error-tracking/capture-exceptions.md new file mode 100644 index 00000000..81e77471 --- /dev/null +++ b/context/agents/error-tracking/capture-exceptions.md @@ -0,0 +1,42 @@ +--- +type: capture-exceptions +flow: error-tracking +label: Wire up exception capture +model_pi: openai/gpt-5.6-terra +effort_pi: low +model_sdk: claude-sonnet-4-6 +effort_sdk: high +skills: [integration-v2-error-tracking-step, posthog-best-practices] +allowedTools: [Read, Write, Edit, Glob, Grep] +disallowedTools: [enqueue_task] +dependsOn: [install, init] +--- + +## Goal + +Make the errors the app does not catch reach PostHog, by whatever means the +SDK offers for that. Which means depends on the SDK: some autocapture +exceptions once you enable it at init, some wire into the framework's own +error handler, some give you a boundary to mount at the app entry. Follow the +docs and the reference example for this one, and set it up in one place — +never manual capture calls sprinkled across files. + +The SDK is installed and initialized — either it already was, or the install +and init tasks before you did it (see their handoffs); build on that, do not +re-check it. + +This is an instrument-only task. Do not install dependencies, run the build, +run tests, or start the app — the user-driven test-setup step at the end of +the flow verifies, when the user wants it. Do not touch the build config +either way; when the flow includes a configure task, it owns those files. +Stay inside this project's directory and set up that one place; that is the +whole job. + +## How you know you succeeded + +An error the app does not catch reaches PostHog, through the mechanism this +SDK gives you rather than one you invented. You did not install anything, run +a build, lint, or tests, search outside the project, or read through the whole +app or hand-wrap individual components or routes. Your handoff names the files +you changed and the capture mechanism, so the report can explain it to the +user. diff --git a/context/agents/error-tracking/configure.md b/context/agents/error-tracking/configure.md new file mode 100644 index 00000000..29228738 --- /dev/null +++ b/context/agents/error-tracking/configure.md @@ -0,0 +1,40 @@ +--- +type: configure +flow: error-tracking +label: Apply build-config changes +model_pi: openai/gpt-5.6-sol +effort_pi: medium +model_sdk: claude-sonnet-4-6 +effort_sdk: medium +skills: [] +allowedTools: [Read, Write, Edit, Glob, Grep, Bash, load_skill_menu, install_skill] +disallowedTools: [enqueue_task] +dependsOn: [capture-exceptions] +--- + +## Goal + +Make this project's production build emit and upload source maps (or, for Go +and Rust, native debug symbols). Install the skill your task input names +(`install_skill` with the `skillId`) and read it — it is the source of truth +for the per-framework build-config and the uploader wiring. + +Two of the skill's steps are yours: + +- **"Apply build-config changes"** — make the bundler / build-config edits the + skill instructs for this platform, so the build produces and injects the + chunk IDs PostHog needs and runs the uploader. +- **"Make credentials available at build time"** — do the skill's step so the + build can read the upload credentials from the environment. If it calls for a + loader (e.g. `dotenv`), install it SILENTLY with the project's package + manager. Skip this step entirely when the platform already auto-loads `.env`. + +Do not write any credential values and do not create env files — the +`credentials` task owns that, in parallel with you. Do not run the build. + +## How you know you succeeded + +The build config carries the skill's source-map / debug-symbol changes and can +read its credentials from the environment at build time. Your handoff names +every file you changed and the exact build-config keys you added, so the CI +task can wire the same variables through the pipeline. diff --git a/context/agents/error-tracking/credentials.md b/context/agents/error-tracking/credentials.md new file mode 100644 index 00000000..ff6ba60a --- /dev/null +++ b/context/agents/error-tracking/credentials.md @@ -0,0 +1,50 @@ +--- +type: credentials +flow: error-tracking +label: Get and write the upload credentials +model_pi: openai/gpt-5.6-sol +effort_pi: medium +model_sdk: claude-sonnet-4-6 +effort_sdk: medium +skills: [] +allowedTools: [Read, Write, Edit, Glob, Grep, Bash, load_skill_menu, install_skill, wizard_ask] +disallowedTools: [enqueue_task] +dependsOn: [] +--- + +## Goal + +Put the PostHog source-map upload credentials into this project's environment. +Install the skill your task input names (`install_skill` with the `skillId`) +and follow its **"Write credentials to the env file"** step for the variable +names and the env file to pick. + +The upload needs a PostHog **personal API key** at build time. Only the user can +mint one — never call the PostHog API or any tool to create it. Get it and +write it in this one task (the key never survives across tasks): + +1. Ask with `wizard_ask`, exactly: + `{ id: "api-key", prompt: "Paste your PostHog personal API key below.\n\nDon't have one yet? Create one here:\n\n\nWhen creating the key, choose the 'Source map upload' preset, then come back and paste it here.", kind: "text", sensitive: true }` + You receive `{ secretRef: "secret:..." }` — a vaulted reference, never the raw + value. If `wizard_ask` is unavailable (non-interactive run), report this task + with status `not needed` and say in your handoff that the user must create + the key and set the variables themselves; do not block. +2. Pick the env file per the skill (reuse the one PostHog's SDK already writes + its `POSTHOG_*` vars to, when there is one). Call `check_env_keys` on it + first (it returns present/absent, never values — never read the file + directly). +3. Call `set_env_values`, passing the secretRef as a value object, not a + literal string — e.g. + `values: { "POSTHOG_CLI_API_KEY": { secretRef: "" }, "POSTHOG_CLI_PROJECT_ID": "", "POSTHOG_CLI_HOST": "" }`. + The exact variable names follow the skill's per-uploader convention. The + wizard resolves the ref locally, so you never see the key value. + +Replace ``, ``, and `` from your project +context. Do not touch the build config — the `configure` task owns that. + +## How you know you succeeded + +The env file holds the upload variables (the key as a resolved secret, the +non-secret project id and host as literals), written through the wizard tools, +never hardcoded in source. Your handoff names the env file and every variable +name — never a value — so the CI task carries the same names into the pipeline. diff --git a/context/agents/error-tracking/init.md b/context/agents/error-tracking/init.md new file mode 100644 index 00000000..97842c04 --- /dev/null +++ b/context/agents/error-tracking/init.md @@ -0,0 +1,33 @@ +--- +type: init +flow: error-tracking +label: Set up PostHog initialization +model_pi: openai/gpt-5.6-terra +effort_pi: low +model_sdk: claude-sonnet-4-6 +effort_sdk: medium +skills: [integration-v2-init, posthog-best-practices] +allowedTools: [Read, Write, Edit, Glob, Grep] +disallowedTools: [enqueue_task] +dependsOn: [] +--- + +## Goal + +Make sure PostHog is initialized. If the project already has a working +`posthog.init(...)` (or the framework's equivalent) with its env keys wired, +leave it alone and say so in your handoff. If it doesn't, create it following +your skill — it owns the how: the framework's init point, the env-var wiring +through the wizard tools, and `.env.example`. + +You only exist in this flow because the user asked for error tracking on a +repo without PostHog. Initialize the SDK so exceptions can flow and stop — +no instrumentation, no extras. Don't set up exception capture either way; +the capture-exceptions task after you owns that. + +## How you know you succeeded + +An init point exists with the PostHog env keys present — whether it already +did or you just created it — keys in the env file, never hardcoded. Your +handoff names the files involved and how the client is constructed, so the +capture-exceptions task can find the init options without re-discovering them. diff --git a/context/agents/error-tracking/install.md b/context/agents/error-tracking/install.md new file mode 100644 index 00000000..9f678fad --- /dev/null +++ b/context/agents/error-tracking/install.md @@ -0,0 +1,31 @@ +--- +type: install +flow: error-tracking +label: Add the PostHog SDK to the manifest +model_pi: openai/gpt-5.6-terra +effort_pi: low +model_sdk: claude-haiku-4-5-20251001 +skills: [integration-v2-install] +allowedTools: [Read, Edit, Glob, Grep, Bash] +disallowedTools: [enqueue_task] +dependsOn: [] +--- + +## Goal + +Make sure the PostHog SDK is in the manifest. If it's already installed, +leave it alone and say so in your handoff. If it isn't, install it following +your skill — it owns the how: the package manager rules, the version rules, +what counts as an environment failure, and the fallback. + +You only exist in this flow because the user asked for error tracking on a +repo without PostHog. Install the SDK the errors will report through (the +server library too, if the app runs server-side code) and stop — no +instrumentation, no extras. + +## How you know you succeeded + +The SDK is declared in the manifest at a real version — whether it already +was or you just installed it — or your handoff plainly says why the +environment stopped you. Your handoff names the manifest and the package, so +later steps import it under the name they will actually get. diff --git a/context/agents/error-tracking/report.md b/context/agents/error-tracking/report.md new file mode 100644 index 00000000..3ffc0d74 --- /dev/null +++ b/context/agents/error-tracking/report.md @@ -0,0 +1,64 @@ +--- +type: report +flow: error-tracking +label: Summarise and hand off +sink: true +model_pi: openai/gpt-5.6-luna +effort_pi: low +model_sdk: claude-sonnet-4-6 +effort_sdk: medium +skills: [] +allowedTools: [Read, Glob, Grep, Write, posthog_exec] +disallowedTools: [enqueue_task] +dependsOn: [capture-exceptions, wire-ci, test-setup] +--- + +## Goal + +Tell the user what error tracking now does for them and what they still have +to do, from the handoffs of every task in the run. `read_handoffs` gives you +each task's report — the capture mechanism, the files changed, the env +variable names, the CI secret to create, and any deploy path that could not be +traced. Do not re-derive any of it from the project. + +First, turn on the Error Tracking product for the team (`products-enable` +through `posthog_exec`) so the captured exceptions have a UI to land in. If +the call fails or the tool is missing, carry it as a follow-up — never fail +the report over it. + +Write the hand-off to `posthog-error-tracking-report.md` at the top level of +this project's directory. When the run wired source-map upload, START it with +a **"What you still need to do"** section — numbered, copy-pasteable: + +1. Create a personal API key with the 'Source map upload' preset at + `/settings/user-api-keys` (skip when the credentials handoff says + the key is already written). +2. Add it as the CI secret the wire-ci step referenced, named exactly as in + the pipeline config. +3. Any other manual follow-up the handoffs carry (an untraceable deploy path, + provider-side settings). + +Then cover, briefly and concretely: + +- How uncaught errors reach PostHog now — the capture mechanism and the files + that carry it. +- If the run also installed and initialized the SDK, say so — the user + started this command without PostHog and now has it. +- When source-map upload was wired: the files changed (paths only), the exact + production build command, and that every production build now uploads. +- When it was skipped: one line saying why (readable stack traces on this + platform) — an outcome, not an apology. +- How to verify: trigger any error and look at + `/project//error_tracking`; uploaded symbol sets appear + at `/project//error_tracking/configuration`. + +Never write a secret value into the report — only variable names. Replace +`` and `` from your project context. Give the same +summary in chat. + +## How you know you succeeded + +`posthog-error-tracking-report.md` exists and a user who reads only it knows +how errors reach PostHog, the follow-ups they still owe (the API key and the +CI secret named exactly, when upload was wired), and where in PostHog to see +the first captured exception. diff --git a/context/agents/error-tracking/setup-error-tracking.md b/context/agents/error-tracking/setup-error-tracking.md new file mode 100644 index 00000000..4d252e89 --- /dev/null +++ b/context/agents/error-tracking/setup-error-tracking.md @@ -0,0 +1,78 @@ +--- +type: setup-error-tracking +flow: error-tracking +seed: true +model_pi: openai/gpt-5.6-terra +effort_pi: medium +model_sdk: claude-sonnet-4-6 +effort_sdk: high +skills: [] +allowedTools: [Read, Glob, Grep, posthog_exec] +disallowedTools: [Write, Edit, Bash, complete_task] +dependsOn: [] +--- + +## Goal + +Plan a PostHog Error Tracking setup and seed the task queue. The end state: +errors the app does not catch reach PostHog, and — where the platform ships +minified bundles or stripped binaries — production builds upload the source +maps or debug symbols that make the stack traces readable. + +First establish two facts from the repo: + +**1. Is PostHog already integrated?** Look for `posthog-js` or a server SDK in +the dependency manifests, or a `posthog.init(...)` / snippet in the source. +Check the project state for existing events if the repo is ambiguous. + +**2. Which uploader variant is this project — or none?** Read the manifests +and pick at most one, by this precedence (first match wins): + +- `pubspec.yaml` → `flutter` +- an `.xcodeproj`, `Podfile`, or `Package.swift` → `ios` +- a Gradle build file (`build.gradle`, `build.gradle.kts`, `settings.gradle`) → `android` +- `go.mod` → `go` +- `Cargo.toml` → `rust` +- otherwise read `package.json` dependencies, first match wins: + `react-native` → `react-native`; `nuxt` → `nuxt`; `next` → `nextjs`; + `@angular/core` → `angular`; `vite` → `vite`; `webpack` → `webpack`; + `rollup` → `rollup`; `react` → `react`; server-only Node → `node`; + any other browser JS → `web` +- **none** for platforms whose stack traces are already readable: plain + Python (Django, Flask, FastAPI), Ruby, PHP, Elixir, JVM servers, .NET. + Skip the whole upload subgraph for them — a skipped upload on such a + platform is an outcome, not a gap. + +When a variant matched, the uploader skill id is +`error-tracking-upload-source-maps-`. Pass it to the four upload +tasks as `inputs: { skillId: "", displayName: "" }` +so no task re-detects. + +Then seed the graph: + +- `install` and `init`, independent of each other — **only when PostHog is + not integrated**. Do not stop on an uninstrumented repo, integrate. +- `capture-exceptions`, after `install` and `init` (with no dependencies when + PostHog was already integrated). +- When an uploader variant matched, add the upload subgraph: + - `credentials`, no dependencies — it stops to ask the user for a personal + API key, so keep it a root task: the prompt reaches the user early while + the code tasks run. + - `configure`, after `capture-exceptions` — build-config changes; it runs + after the code edits so the two never fight over the same files. + - `wire-ci`, after `configure` and `credentials`. + - `test-setup`, after `wire-ci` — offers the user a local end-to-end test + last, once everything is wired. +- `report`, after every other queued task. It writes the handoff last, so it + describes what actually shipped. + +Never plan an identify, capture, dashboard, or session-replay task — this run +sets up error tracking, not the full integration. The minimal SDK footprint +that `install` and `init` leave behind is enough for exceptions to flow. + +## How you know you succeeded + +Every task in the chosen graph is queued with that dependency shape, the four +upload tasks (when queued) share the same `{ skillId, displayName }` inputs, +`report` depends on the rest (directly or transitively), and the first task is +runnable. Keep labels short — the action in a few words. diff --git a/context/agents/error-tracking/test-setup.md b/context/agents/error-tracking/test-setup.md new file mode 100644 index 00000000..b5d6dfb2 --- /dev/null +++ b/context/agents/error-tracking/test-setup.md @@ -0,0 +1,41 @@ +--- +type: test-setup +flow: error-tracking +label: Offer to test the local setup +model_pi: openai/gpt-5.6-sol +effort_pi: medium +model_sdk: claude-sonnet-4-6 +effort_sdk: high +skills: [] +allowedTools: [Read, Write, Edit, Glob, Grep, Bash, load_skill_menu, install_skill, wizard_ask] +disallowedTools: [enqueue_task] +dependsOn: [wire-ci] +--- + +## Goal + +Offer the user a one-time, end-to-end check that errors reach PostHog with +readable stack traces. Install the skill your task input names (`install_skill` +with the `skillId`) and follow its **"Test the local setup"** step for the +platform-appropriate affordance, the `captureException` shape, the placement, +and the read-before-edit / always-revert rules. + +First ask with `wizard_ask`: +`{ id: "test-affordance", prompt: "Want me to help you test your local setup? I'll add a temporary test button (or route) to your app so you can confirm errors show up in Error Tracking with readable stack traces after your next build. I'll remove it once you've confirmed it works.", kind: "single", options: [{ label: "Yes, help me test it", value: "yes" }, { label: "No, I'll test on my own later", value: "no" }] }` + +- **"no"** (or `wizard_ask` unavailable): do nothing to the code and report this + task done, noting the test was offered and declined. +- **"yes"**: add the affordance per the skill, then pause with a second + `wizard_ask` (id `"test-done"`, a single `Continue (revert test code)` + option) whose prompt gives the build, run, and Error-Tracking-check as + literal numbered steps (build first — it uploads the maps — then trigger the + affordance, then confirm the error resolves to real source in Error + Tracking). After the user continues, REVERT every test edit per the skill's + rules. Never leave the affordance in place, even if the user says it didn't + work — revert first, then carry the failure into your handoff. + +## How you know you succeeded + +Either the user declined and no code changed, or the affordance was added, +tested, and fully reverted. Your handoff says which, and carries any failure +the user reported for the report to surface. diff --git a/context/agents/error-tracking/wire-ci.md b/context/agents/error-tracking/wire-ci.md new file mode 100644 index 00000000..a0ac48f2 --- /dev/null +++ b/context/agents/error-tracking/wire-ci.md @@ -0,0 +1,37 @@ +--- +type: wire-ci +flow: error-tracking +label: Set up CI for automatic uploads +model_pi: openai/gpt-5.6-sol +effort_pi: high +model_sdk: claude-sonnet-4-6 +effort_sdk: high +skills: [] +allowedTools: [Read, Write, Edit, Glob, Grep, load_skill_menu, install_skill] +disallowedTools: [enqueue_task] +dependsOn: [configure, credentials] +--- + +## Goal + +Make the credentials reach the production build wherever it actually runs, so +source maps upload on every deploy — not just on a local build. Install the +skill your task input names (`install_skill` with the `skillId`) and follow its +**"Set up CI for automatic uploads"** step — it owns tracing where the +production build runs and wiring the credentials through every layer, whatever +the CI provider. + +The `configure` and `credentials` handoffs already name the build-config keys +and the exact environment-variable names in use — carry those same names into +the pipeline; do not invent new ones. Trace the deploy path by reading the +project's own files (CI workflows, Dockerfiles, deploy scripts) — never invent +config that is not there. You cannot create the CI secret that holds the API +key; reference it by name and carry that follow-up, plus any deploy path you +could not trace, into your handoff for the report. + +## How you know you succeeded + +The pipeline that runs the production build carries the upload credentials by +the same names the credentials task used, and every secret the user still has +to create is named in your handoff. Your handoff lists the CI files you changed +and every manual follow-up, so the report can hand them to the user. From 644a7a7d386dfb9f7d3cb8242c0f3e901144d595 Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Tue, 1 Sep 2026 18:21:22 +0200 Subject: [PATCH 02/35] feat(agents): run the error-tracking code tasks on sol for the pi harness MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit install/init move terra→sol low, capture-exceptions terra low→sol medium. Pairs with the wizard binding change routing the program to the orchestrator on pi; seed stays terra and report stays luna, matching the metrics flow's pattern. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01PVXmLWB31CBujD2FJ2ogvY --- context/agents/error-tracking/capture-exceptions.md | 4 ++-- context/agents/error-tracking/init.md | 2 +- context/agents/error-tracking/install.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/context/agents/error-tracking/capture-exceptions.md b/context/agents/error-tracking/capture-exceptions.md index 81e77471..5c14e6d5 100644 --- a/context/agents/error-tracking/capture-exceptions.md +++ b/context/agents/error-tracking/capture-exceptions.md @@ -2,8 +2,8 @@ type: capture-exceptions flow: error-tracking label: Wire up exception capture -model_pi: openai/gpt-5.6-terra -effort_pi: low +model_pi: openai/gpt-5.6-sol +effort_pi: medium model_sdk: claude-sonnet-4-6 effort_sdk: high skills: [integration-v2-error-tracking-step, posthog-best-practices] diff --git a/context/agents/error-tracking/init.md b/context/agents/error-tracking/init.md index 97842c04..608a3bec 100644 --- a/context/agents/error-tracking/init.md +++ b/context/agents/error-tracking/init.md @@ -2,7 +2,7 @@ type: init flow: error-tracking label: Set up PostHog initialization -model_pi: openai/gpt-5.6-terra +model_pi: openai/gpt-5.6-sol effort_pi: low model_sdk: claude-sonnet-4-6 effort_sdk: medium diff --git a/context/agents/error-tracking/install.md b/context/agents/error-tracking/install.md index 9f678fad..14a2e7ff 100644 --- a/context/agents/error-tracking/install.md +++ b/context/agents/error-tracking/install.md @@ -2,7 +2,7 @@ type: install flow: error-tracking label: Add the PostHog SDK to the manifest -model_pi: openai/gpt-5.6-terra +model_pi: openai/gpt-5.6-sol effort_pi: low model_sdk: claude-haiku-4-5-20251001 skills: [integration-v2-install] From 870f6970a0e96f28d321e0a15af0e8ccf9d27827 Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Wed, 2 Sep 2026 10:49:09 +0200 Subject: [PATCH 03/35] fix(skills): make source-map installs follow the project's package manager A configure run in a pnpm workspace copied the docs' npm install and npm hard-fails on workspace:* deps (EUNSUPPORTEDPROTOCOL); the agent retried npm three ways and never switched. New skill bullet: detect the manager from the lockfile, translate the docs' npm commands, and read EUNSUPPORTEDPROTOCOL as wrong-manager, never a flag to retry. The error-tracking flow's configure task now also mandates detect_package_manager before the first install. Verified: the pnpm monorepo fixture run that failed now completes 4/4, recovering to `pnpm add -D` on first contact with the error. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01PVXmLWB31CBujD2FJ2ogvY --- context/agents/error-tracking/configure.md | 7 +++++++ .../error-tracking-upload-source-maps/description.md | 1 + 2 files changed, 8 insertions(+) diff --git a/context/agents/error-tracking/configure.md b/context/agents/error-tracking/configure.md index 29228738..3b0e7e42 100644 --- a/context/agents/error-tracking/configure.md +++ b/context/agents/error-tracking/configure.md @@ -29,6 +29,13 @@ Two of the skill's steps are yours: loader (e.g. `dotenv`), install it SILENTLY with the project's package manager. Skip this step entirely when the platform already auto-loads `.env`. +Install every dependency with the project's own package manager: call +`detect_package_manager` before the first install and use its answer, +translating any `npm install` the skill or docs show (`pnpm add -D …`, +`yarn add -D …`). In a pnpm or yarn workspace, npm fails outright on +`workspace:*` dependencies (`EUNSUPPORTEDPROTOCOL`) — that error means the +wrong manager, never a flag to retry with. + Do not write any credential values and do not create env files — the `credentials` task owns that, in parallel with you. Do not run the build. diff --git a/context/skills/error-tracking-upload-source-maps/description.md b/context/skills/error-tracking-upload-source-maps/description.md index bd355029..90078145 100644 --- a/context/skills/error-tracking-upload-source-maps/description.md +++ b/context/skills/error-tracking-upload-source-maps/description.md @@ -34,6 +34,7 @@ Wire source map generation, chunk-ID injection, and upload into your **productio - **Don't ship source maps publicly**: omit `.map` files from the deployed artifact, or use hidden source maps. Uploaded maps live in PostHog, not on your origin. - **Link each release to its commit.** The CLI auto-detects the commit from the CI's git env vars — see "Associate the release with a git commit" for making those reachable in Docker/CI builds. - **Never write a dependency version from memory.** When a variant needs the CLI or a plugin *inside the project* — the JS/web build tools (Node, web, Next.js, React, Angular, Nuxt, Vite, Webpack, Rollup) — install it with the project's package manager pinned to `latest` (`npm install --save-dev @posthog/cli@latest`, `pnpm add -D @posthog/cli@latest`, `yarn add -D @posthog/cli@latest`) and let the manager write the resolved version into `package.json`. A version recalled from memory is usually far behind and silently breaks the commands in this skill — `--dotenv-file`, for one, is rejected outright by 0.5.x. iOS, Android, React Native, Flutter, Go and Rust are the exception: the wizard pre-installs a global `posthog-cli` for those, so add no project dependency for them at all. +- **Match the project's package manager, not the docs'.** Example commands in the docs install with `npm`. Before any install, detect the real manager from the lockfile — `pnpm-lock.yaml` → `pnpm add -D`, `yarn.lock` → `yarn add -D`, `package-lock.json` / none → `npm install --save-dev` (the wizard's `detect_package_manager` tool answers this) — and translate the command. This is load-bearing in monorepos: npm hard-fails on `workspace:*` dependencies with `EUNSUPPORTEDPROTOCOL`, so an npm install copied into a pnpm workspace cannot succeed, and no npm flag fixes it — switch the command to the detected manager instead of retrying. #### Examples - **Node / tsc** Emit maps with embedded sources by setting both in `tsconfig.json`: `"sourceMap": true` and `"inlineSources": true`. Add the CLI to the project with `npm install --save-dev @posthog/cli@latest` (or the project's package manager) so the build script and CI resolve the same binary — never hand-write the version string. Then run `posthog-cli sourcemap process` against the build output dir as a post-build step — it injects chunk IDs and uploads in one pass, and needs the upload credentials (see "Make credentials available at build time"). From c59bda31f0c179bf4b5211bc949f79a27ec022e9 Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Wed, 2 Sep 2026 10:55:43 +0200 Subject: [PATCH 04/35] fix(agents): make the error-tracking seed settle both facts before planning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A seed run saw "PostHog already integrated" and enqueued only capture+report, never applying the uploader precedence to a tsc-built Node service — while an identical sibling fixture planned the full graph. The two facts are now declared independent, a compiled/bundled JS project is explicitly never "none", and the success criteria demand the plan state the uploader decision (or the readable-stack reason) explicitly. Verified: the fixture that under-planned now queues the full node-uploader graph and completes 5/5. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01PVXmLWB31CBujD2FJ2ogvY --- .../agents/error-tracking/setup-error-tracking.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/context/agents/error-tracking/setup-error-tracking.md b/context/agents/error-tracking/setup-error-tracking.md index 4d252e89..3fcf6458 100644 --- a/context/agents/error-tracking/setup-error-tracking.md +++ b/context/agents/error-tracking/setup-error-tracking.md @@ -48,6 +48,14 @@ When a variant matched, the uploader skill id is tasks as `inputs: { skillId: "", displayName: "" }` so no task re-detects. +The two facts are independent — settle BOTH before you enqueue anything. +"PostHog is already integrated" answers fact 1 only; it never decides fact 2, +and an already-integrated project still gets the upload subgraph when a +variant matches. A compiled or bundled JS project always has one: a Node +service built with `tsc` ships minified/compiled output, so it is the `node` +variant, not "none". Only the readable-stack platforms listed above skip the +subgraph. + Then seed the graph: - `install` and `init`, independent of each other — **only when PostHog is @@ -75,4 +83,8 @@ that `install` and `init` leave behind is enough for exceptions to flow. Every task in the chosen graph is queued with that dependency shape, the four upload tasks (when queued) share the same `{ skillId, displayName }` inputs, `report` depends on the rest (directly or transitively), and the first task is -runnable. Keep labels short — the action in a few words. +runnable. Your plan states both facts explicitly: whether PostHog was +integrated, and which uploader variant matched — or, when you queue no upload +tasks, which readable-stack platform this is and why no variant applies. A +plan that never mentions fact 2 is an incomplete plan, not a decision. Keep +labels short — the action in a few words. From 8be48c840a64bd4317c77b8784fc0cb7ba07885b Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Thu, 3 Sep 2026 11:12:07 +0200 Subject: [PATCH 05/35] feat(agents): document upload variable names in .env.example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The integration init skill documents its public keys in .env.example; the credentials task wrote its upload variables only to the real env file, so the names never reached the committed template. New step 4: append the names with placeholders (never values) — for the next developer, and so a later run's check_env_keys reports the expectation. Scoped to the error-tracking flow's task prompt; the shared uploader skill is untouched, so the standalone upload-source-maps program keeps its current behavior. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01PVXmLWB31CBujD2FJ2ogvY --- context/agents/error-tracking/credentials.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/context/agents/error-tracking/credentials.md b/context/agents/error-tracking/credentials.md index ff6ba60a..dea87ae3 100644 --- a/context/agents/error-tracking/credentials.md +++ b/context/agents/error-tracking/credentials.md @@ -38,6 +38,12 @@ write it in this one task (the key never survives across tasks): `values: { "POSTHOG_CLI_API_KEY": { secretRef: "" }, "POSTHOG_CLI_PROJECT_ID": "", "POSTHOG_CLI_HOST": "" }`. The exact variable names follow the skill's per-uploader convention. The wizard resolves the ref locally, so you never see the key value. +4. Document the same variable names for other developers: append them to + `.env.example` (create it if the project has none) with empty or + placeholder values — never a real value, and never the key itself. The + example file is committed and is the only `.env*` you may write directly; + it is how the next developer, and the next wizard run's `check_env_keys`, + learns the project expects these variables. Replace ``, ``, and `` from your project context. Do not touch the build config — the `configure` task owns that. @@ -46,5 +52,6 @@ context. Do not touch the build config — the `configure` task owns that. The env file holds the upload variables (the key as a resolved secret, the non-secret project id and host as literals), written through the wizard tools, -never hardcoded in source. Your handoff names the env file and every variable -name — never a value — so the CI task carries the same names into the pipeline. +never hardcoded in source, and `.env.example` documents the same names with +placeholders. Your handoff names the env file and every variable name — never +a value — so the CI task carries the same names into the pipeline. From 2b067cca9db4283f1062a631ca122c638c4491b5 Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Thu, 3 Sep 2026 12:47:03 +0200 Subject: [PATCH 06/35] feat(agents): skip the source-map uploader on Astro Astro inlines scripts below its asset limit into the HTML, so a build can emit a .map with no .js beside it. posthog-cli then finds zero pairs, exits non-zero, and takes the whole build down with it. Treat Astro as a no-variant platform: install and exception capture still run, the build script is left untouched, and the report says why upload was skipped. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01PVXmLWB31CBujD2FJ2ogvY --- context/agents/error-tracking/report.md | 5 +++-- .../agents/error-tracking/setup-error-tracking.md | 14 ++++++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/context/agents/error-tracking/report.md b/context/agents/error-tracking/report.md index 3ffc0d74..366611a0 100644 --- a/context/agents/error-tracking/report.md +++ b/context/agents/error-tracking/report.md @@ -46,8 +46,9 @@ Then cover, briefly and concretely: started this command without PostHog and now has it. - When source-map upload was wired: the files changed (paths only), the exact production build command, and that every production build now uploads. -- When it was skipped: one line saying why (readable stack traces on this - platform) — an outcome, not an apology. +- When it was skipped: one line saying why — readable stack traces on this + platform, or that Astro is not supported by the uploader. An outcome, not an + apology. Say plainly that the build command was left untouched. - How to verify: trigger any error and look at `/project//error_tracking`; uploaded symbol sets appear at `/project//error_tracking/configuration`. diff --git a/context/agents/error-tracking/setup-error-tracking.md b/context/agents/error-tracking/setup-error-tracking.md index 3fcf6458..f7ac0ce4 100644 --- a/context/agents/error-tracking/setup-error-tracking.md +++ b/context/agents/error-tracking/setup-error-tracking.md @@ -33,6 +33,11 @@ and pick at most one, by this precedence (first match wins): - a Gradle build file (`build.gradle`, `build.gradle.kts`, `settings.gradle`) → `android` - `go.mod` → `go` - `Cargo.toml` → `rust` +- `astro` in `package.json` dependencies → **none**. Astro is not supported by + the uploader: it inlines scripts below its asset limit into the HTML, so a + build routinely emits a `.map` with no `.js` beside it, and the upload step + then fails the whole build. This rule wins over every `package.json` match + below — an Astro project that also depends on `vite` is still **none**. - otherwise read `package.json` dependencies, first match wins: `react-native` → `react-native`; `nuxt` → `nuxt`; `next` → `nextjs`; `@angular/core` → `angular`; `vite` → `vite`; `webpack` → `webpack`; @@ -51,10 +56,10 @@ so no task re-detects. The two facts are independent — settle BOTH before you enqueue anything. "PostHog is already integrated" answers fact 1 only; it never decides fact 2, and an already-integrated project still gets the upload subgraph when a -variant matches. A compiled or bundled JS project always has one: a Node +variant matches. A compiled or bundled JS project normally has one: a Node service built with `tsc` ships minified/compiled output, so it is the `node` -variant, not "none". Only the readable-stack platforms listed above skip the -subgraph. +variant, not "none". Only two kinds of project skip the subgraph — the +readable-stack platforms listed above, and Astro. Then seed the graph: @@ -85,6 +90,7 @@ upload tasks (when queued) share the same `{ skillId, displayName }` inputs, `report` depends on the rest (directly or transitively), and the first task is runnable. Your plan states both facts explicitly: whether PostHog was integrated, and which uploader variant matched — or, when you queue no upload -tasks, which readable-stack platform this is and why no variant applies. A +tasks, why no variant applies: which readable-stack platform this is, or that +Astro is not supported by the uploader. A plan that never mentions fact 2 is an incomplete plan, not a decision. Keep labels short — the action in a few words. From 64955b18d9117bf12f12f5c838134783095fe75a Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Tue, 8 Sep 2026 19:31:22 +0200 Subject: [PATCH 07/35] fix(agents): make the error-tracking init wire .env loading on plain Node The shared integration-v2-init skill assumes the framework auto-loads .env, which is true for Next/Nuxt/Astro/SvelteKit/Vite but not for plain Node backends. On Express, Fastify, Hono, Koa and raw node:http the run wrote process.env reads and a .env file with nothing to bridge them, so every app died at module load on its own missing-variable guard. Teach the init task to wire the loading (dotenv, or --env-file) and to start the app once to confirm it boots. Also have configure check that some script actually runs a newly emitted bundle, since two apps built to dist/ while start still ran the source. Scoped to this flow's task prompts; the shared skill is untouched. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01PVXmLWB31CBujD2FJ2ogvY --- context/agents/error-tracking/configure.md | 6 ++++ context/agents/error-tracking/init.md | 33 +++++++++++++++++++--- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/context/agents/error-tracking/configure.md b/context/agents/error-tracking/configure.md index 3b0e7e42..e3aec438 100644 --- a/context/agents/error-tracking/configure.md +++ b/context/agents/error-tracking/configure.md @@ -36,6 +36,12 @@ translating any `npm install` the skill or docs show (`pnpm add -D …`, `workspace:*` dependencies (`EUNSUPPORTEDPROTOCOL`) — that error means the wrong manager, never a flag to retry with. +When your changes make the build emit a bundle to a new directory (`dist/`, +`build/`), check that some script actually runs that output. A project whose +`build` writes `dist/index.js` while `start` still runs the original source +never executes the bundle the maps were uploaded for, so every uploaded map +goes unused. Add or fix the script that serves the built output. + Do not write any credential values and do not create env files — the `credentials` task owns that, in parallel with you. Do not run the build. diff --git a/context/agents/error-tracking/init.md b/context/agents/error-tracking/init.md index 608a3bec..086ac03d 100644 --- a/context/agents/error-tracking/init.md +++ b/context/agents/error-tracking/init.md @@ -7,7 +7,7 @@ effort_pi: low model_sdk: claude-sonnet-4-6 effort_sdk: medium skills: [integration-v2-init, posthog-best-practices] -allowedTools: [Read, Write, Edit, Glob, Grep] +allowedTools: [Read, Write, Edit, Glob, Grep, Bash] disallowedTools: [enqueue_task] dependsOn: [] --- @@ -25,9 +25,34 @@ repo without PostHog. Initialize the SDK so exceptions can flow and stop — no instrumentation, no extras. Don't set up exception capture either way; the capture-exceptions task after you owns that. +## Make the environment actually reachable + +Writing the keys to `.env` is only half the job — something has to load that +file at runtime, or the app throws on boot and captures nothing. + +Most frameworks do it for you: Next, Nuxt, Astro and SvelteKit auto-load `.env`, +and Vite auto-loads it for client code. Nothing to do there. + +A plain Node backend does not — Express, Fastify, Hono, Koa, a raw `node:http` +server — and neither does a bare Rollup or webpack config. When your init point +reads `process.env` on one of those, wire the loading too, either way: + +- install `dotenv` with the project's own package manager (detect it from the + lockfile) and import it above the PostHog init — `require('dotenv').config()`, + or `import 'dotenv/config'` for ESM; or +- add `--env-file=.env` to the `start` and `dev` scripts, when the project is on + Node 20.6+ and would rather not take a new dependency. + +Then start the app once and confirm it boots. An app that exits at module load +with `POSTHOG_… variable required by PostHog is missing or un-configured` is a +failed init, not a finished one — that message is the guard you wrote firing +against an env file nothing reads. + ## How you know you succeeded An init point exists with the PostHog env keys present — whether it already -did or you just created it — keys in the env file, never hardcoded. Your -handoff names the files involved and how the client is constructed, so the -capture-exceptions task can find the init options without re-discovering them. +did or you just created it — keys in the env file, never hardcoded. On a +platform that does not auto-load `.env`, the loading is wired and the app +starts cleanly. Your handoff names the files involved, how the client is +constructed, and how `.env` reaches it, so the capture-exceptions task can find +the init options without re-discovering them. From 31cc72664b9291cfb96ca83ec39433be7115d19d Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Tue, 8 Sep 2026 19:53:26 +0200 Subject: [PATCH 08/35] fix(agents): stop the error-tracking init inventing import.meta.env on Angular On the stock @angular/build builder nothing defines process.env or import.meta.env in the browser bundle, but the run wrote import.meta.env['NG_APP_POSTHOG_PROJECT_TOKEN'] into src/environments/*. Reading a property off undefined throws while the module evaluates, so stayed empty and the app rendered a blank page. Name the mechanism's real source (@ngx-env/builder supplies import.meta.env and the NG_APP_ prefix) and require it to be an existing dependency before use. Otherwise fall back to the skill's existing "no valid environment to read from" case: embed the public project token in the committed environment files, which is where it ends up in the bundle anyway. Scoped to this flow's task prompt; the shared skill is untouched. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01PVXmLWB31CBujD2FJ2ogvY --- context/agents/error-tracking/init.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/context/agents/error-tracking/init.md b/context/agents/error-tracking/init.md index 086ac03d..9e3ddafd 100644 --- a/context/agents/error-tracking/init.md +++ b/context/agents/error-tracking/init.md @@ -48,6 +48,17 @@ with `POSTHOG_… variable required by PostHog is missing or un-configured` is a failed init, not a finished one — that message is the guard you wrote firing against an env file nothing reads. +Some platforms have no environment to read at all, and there the answer is not +a loader. Angular on the stock `@angular/build` builder is the common one: +nothing defines `process.env` or `import.meta.env` in the browser bundle, so a +config reading either one throws while the module evaluates and the app renders +a blank page. Never invent the mechanism — `import.meta.env` and the `NG_APP_` +prefix come from `@ngx-env/builder`, so use them only when that dependency is +already in `package.json`. When the project has no such mechanism, put the real +public project token straight into the committed `src/environments/*` files; +this is the skill's "no valid environment to read from" case, and the public +token is publishable — it ships inside the browser bundle either way. + ## How you know you succeeded An init point exists with the PostHog env keys present — whether it already From 5d77c07854bd443096c392cdb868c05ff039ff40 Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Tue, 8 Sep 2026 20:19:43 +0200 Subject: [PATCH 09/35] fix(agents): require the Angular init key to resolve, not just exist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The earlier rule stopped the run reaching for import.meta.env, but it then read env['NG_APP_POSTHOG_PROJECT_TOKEN'] from the project's generated src/environments/.env.ts, which only defines npm_package_version. The key came back undefined, and because a production build takes the guard's quiet return path the app rendered normally while PostHog never initialised — no requests at all. Name that second empty container, require the lookup's source to be opened and the key seen before wiring it, and make the task prove initialisation by serving the build and confirming a request reaches the PostHog host. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01X5Kw3YahYz9RiFeJ8r2hxY --- context/agents/error-tracking/init.md | 28 +++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/context/agents/error-tracking/init.md b/context/agents/error-tracking/init.md index 9e3ddafd..c63ca71f 100644 --- a/context/agents/error-tracking/init.md +++ b/context/agents/error-tracking/init.md @@ -50,14 +50,26 @@ against an env file nothing reads. Some platforms have no environment to read at all, and there the answer is not a loader. Angular on the stock `@angular/build` builder is the common one: -nothing defines `process.env` or `import.meta.env` in the browser bundle, so a -config reading either one throws while the module evaluates and the app renders -a blank page. Never invent the mechanism — `import.meta.env` and the `NG_APP_` -prefix come from `@ngx-env/builder`, so use them only when that dependency is -already in `package.json`. When the project has no such mechanism, put the real -public project token straight into the committed `src/environments/*` files; -this is the skill's "no valid environment to read from" case, and the public -token is publishable — it ships inside the browser bundle either way. +nothing populates `process.env`, `import.meta.env`, or the project's own +`src/environments/.env.ts` with your keys. + +The trap is that each of those *looks* like a mechanism. `import.meta.env` and +the `NG_APP_` prefix only exist with `@ngx-env/builder` installed, and a +generated `.env.ts` usually carries one unrelated key such as +`npm_package_version` and nothing else. Read from either and your key is +`undefined`: in development the guard throws while the module evaluates and the +app renders a blank page, and in a production build the guard returns quietly, +so the app looks fine while PostHog never initialises at all. + +So do not wire a lookup unless you have opened the thing it reads from and seen +your key defined there. When nothing populates it, write the real public project +token as a literal in the committed `src/environments/*` files. This is the +skill's "no valid environment to read from" case, and the public token is +publishable — it ships inside the browser bundle either way. + +Prove it before you finish: build, serve the built output, load a page, and +confirm a request goes to the PostHog host. An app that renders but sends +nothing has not been initialised. ## How you know you succeeded From e79c007e0ed8fa1b338b49115ab1fb656d378475 Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Tue, 8 Sep 2026 20:42:27 +0200 Subject: [PATCH 10/35] fix(agents): make configure prove the built bundle actually runs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pointing `start` at dist/index.js is only safe if that bundle can execute. On koa-notes the esbuild command had no --format=esm while package.json sets "type": "module", so the emitted CommonJS died at boot with `ReferenceError: module is not defined in ES module scope` — a breakage that only appeared once `start` stopped running the source. Require the task to run the build and start the script once, and name the module-format mismatch as the usual cause. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01X5Kw3YahYz9RiFeJ8r2hxY --- context/agents/error-tracking/configure.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/context/agents/error-tracking/configure.md b/context/agents/error-tracking/configure.md index e3aec438..9484ed32 100644 --- a/context/agents/error-tracking/configure.md +++ b/context/agents/error-tracking/configure.md @@ -42,6 +42,14 @@ When your changes make the build emit a bundle to a new directory (`dist/`, never executes the bundle the maps were uploaded for, so every uploaded map goes unused. Add or fix the script that serves the built output. +Then run the build and start that script once. Pointing `start` at a bundle +the project cannot execute is worse than leaving it on the source, because the +breakage only shows up at boot. The usual cause is a module-format mismatch: +`esbuild --platform=node` emits CommonJS unless you pass `--format=esm`, so in a +package with `"type": "module"` the bundle dies with +`ReferenceError: module is not defined in ES module scope`. Match the bundle's +format to the package's type. + Do not write any credential values and do not create env files — the `credentials` task owns that, in parallel with you. Do not run the build. From 9b14ab195ff6dd777d28080b661e250f8e6a44cd Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Tue, 8 Sep 2026 20:42:44 +0200 Subject: [PATCH 11/35] fix(agents): reconcile the configure build-verification with the no-build rule The new "run the build and start that script once" instruction sat directly above "Do not run the build", leaving the task with contradictory orders. Carve the single verification out of the prohibition. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01X5Kw3YahYz9RiFeJ8r2hxY --- context/agents/error-tracking/configure.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/context/agents/error-tracking/configure.md b/context/agents/error-tracking/configure.md index 9484ed32..81c5680e 100644 --- a/context/agents/error-tracking/configure.md +++ b/context/agents/error-tracking/configure.md @@ -51,7 +51,8 @@ package with `"type": "module"` the bundle dies with format to the package's type. Do not write any credential values and do not create env files — the -`credentials` task owns that, in parallel with you. Do not run the build. +`credentials` task owns that, in parallel with you. Apart from the one +verification above, do not run the build. ## How you know you succeeded From 6223e9aea137a42ef3a475a5d3e6d0512f9296c5 Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Wed, 9 Sep 2026 10:38:52 +0200 Subject: [PATCH 12/35] fix(agents): drop the build/run verification steps from error-tracking The agent runs in a sandbox that cannot build or start the project, so rules telling init to boot the app and configure to run the built script were not executable. Keep the knowledge, drop the execution: the env loader and the module format must be correct by construction, checked by reading package.json's "type" and by opening the file a key lookup reads from before wiring it. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01X5Kw3YahYz9RiFeJ8r2hxY --- context/agents/error-tracking/configure.md | 16 +++++++--------- context/agents/error-tracking/init.md | 16 +++++++--------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/context/agents/error-tracking/configure.md b/context/agents/error-tracking/configure.md index 81c5680e..0c1a5515 100644 --- a/context/agents/error-tracking/configure.md +++ b/context/agents/error-tracking/configure.md @@ -42,17 +42,15 @@ When your changes make the build emit a bundle to a new directory (`dist/`, never executes the bundle the maps were uploaded for, so every uploaded map goes unused. Add or fix the script that serves the built output. -Then run the build and start that script once. Pointing `start` at a bundle -the project cannot execute is worse than leaving it on the source, because the -breakage only shows up at boot. The usual cause is a module-format mismatch: -`esbuild --platform=node` emits CommonJS unless you pass `--format=esm`, so in a -package with `"type": "module"` the bundle dies with -`ReferenceError: module is not defined in ES module scope`. Match the bundle's -format to the package's type. +Match the bundle's module format to the package's type while you write the +command — you cannot run it, so it has to be right by construction. `esbuild +--platform=node` emits CommonJS unless you pass `--format=esm`, so in a package +whose `package.json` sets `"type": "module"` the bundle dies at boot with +`ReferenceError: module is not defined in ES module scope`. Read the `type` +field before writing the command. Do not write any credential values and do not create env files — the -`credentials` task owns that, in parallel with you. Apart from the one -verification above, do not run the build. +`credentials` task owns that, in parallel with you. Do not run the build. ## How you know you succeeded diff --git a/context/agents/error-tracking/init.md b/context/agents/error-tracking/init.md index c63ca71f..f1d4295f 100644 --- a/context/agents/error-tracking/init.md +++ b/context/agents/error-tracking/init.md @@ -43,10 +43,10 @@ reads `process.env` on one of those, wire the loading too, either way: - add `--env-file=.env` to the `start` and `dev` scripts, when the project is on Node 20.6+ and would rather not take a new dependency. -Then start the app once and confirm it boots. An app that exits at module load -with `POSTHOG_… variable required by PostHog is missing or un-configured` is a -failed init, not a finished one — that message is the guard you wrote firing -against an env file nothing reads. +You cannot run the app, so this has to be right by construction. Writing the +guard without the loader is what produces +`POSTHOG_… variable required by PostHog is missing or un-configured` at module +load: the guard you wrote firing against an env file nothing reads. Some platforms have no environment to read at all, and there the answer is not a loader. Angular on the stock `@angular/build` builder is the common one: @@ -67,15 +67,13 @@ token as a literal in the committed `src/environments/*` files. This is the skill's "no valid environment to read from" case, and the public token is publishable — it ships inside the browser bundle either way. -Prove it before you finish: build, serve the built output, load a page, and -confirm a request goes to the PostHog host. An app that renders but sends -nothing has not been initialised. ## How you know you succeeded An init point exists with the PostHog env keys present — whether it already did or you just created it — keys in the env file, never hardcoded. On a -platform that does not auto-load `.env`, the loading is wired and the app -starts cleanly. Your handoff names the files involved, how the client is +platform that does not auto-load `.env`, the loading is wired; on a platform +with no environment at all, the token is a literal rather than a lookup into +something that never defines it. Your handoff names the files involved, how the client is constructed, and how `.env` reaches it, so the capture-exceptions task can find the init options without re-discovering them. From 413c0ae070c39012c33edfa9d7c94a7624395cc5 Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Wed, 9 Sep 2026 11:32:28 +0200 Subject: [PATCH 13/35] fix(agents): make the env variable names an explicit contract MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit credentials and configure run in parallel: one writes the env file, the other writes the code that reads it, and neither could see the other's choice. On Nuxt 4 that split them — the skill's Nuxt reference documents variable names only for the posthog-cli path, so credentials wrote POSTHOG_CLI_* while configure wired the @posthog/nuxt module against POSTHOG_API_KEY. Nothing errored; the upload was simply skipped and every trace stayed minified. Give both tasks the same rule, in terms neither framework nor uploader specific: look for the other half of the contract before writing, adopt it verbatim if it exists, decide only when it does not, and always name the chosen set in full in the handoff. configure gets check_env_keys so it can read names without values. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01X5Kw3YahYz9RiFeJ8r2hxY --- context/agents/error-tracking/configure.md | 22 ++++++++++++- context/agents/error-tracking/credentials.md | 33 +++++++++++++++++--- 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/context/agents/error-tracking/configure.md b/context/agents/error-tracking/configure.md index 0c1a5515..3a559df6 100644 --- a/context/agents/error-tracking/configure.md +++ b/context/agents/error-tracking/configure.md @@ -7,7 +7,7 @@ effort_pi: medium model_sdk: claude-sonnet-4-6 effort_sdk: medium skills: [] -allowedTools: [Read, Write, Edit, Glob, Grep, Bash, load_skill_menu, install_skill] +allowedTools: [Read, Write, Edit, Glob, Grep, Bash, load_skill_menu, install_skill, check_env_keys] disallowedTools: [enqueue_task] dependsOn: [capture-exceptions] --- @@ -49,6 +49,26 @@ whose `package.json` sets `"type": "module"` the bundle dies at boot with `ReferenceError: module is not defined in ES module scope`. Read the `type` field before writing the command. +## The names are a contract + +An env variable only works if the name the code reads is the name in the file. +You write the code that reads them; the `credentials` task writes the file, and +you two run in parallel. So never invent a name that already exists somewhere +else — look for the other half of the contract first, and adopt it: + +- **Call `check_env_keys` before you write the config.** It returns names, never + values. If the PostHog upload variables are already there, make your config + read exactly those names, whatever they are, even when they are not the ones + your skill's example shows. +- **Only when they are absent yet** are you the one deciding. Use the names your + skill documents for the mechanism you are wiring, and name them in your + handoff in full so `credentials` and `wire-ci` can match them. + +Names that merely look plausible are the failure here. A build reading +`POSTHOG_API_KEY` beside an env file holding `POSTHOG_CLI_API_KEY` throws no +error anywhere — the upload is skipped silently, the build looks clean, and +every stack trace stays minified. + Do not write any credential values and do not create env files — the `credentials` task owns that, in parallel with you. Do not run the build. diff --git a/context/agents/error-tracking/credentials.md b/context/agents/error-tracking/credentials.md index dea87ae3..ae86250d 100644 --- a/context/agents/error-tracking/credentials.md +++ b/context/agents/error-tracking/credentials.md @@ -33,11 +33,12 @@ write it in this one task (the key never survives across tasks): its `POSTHOG_*` vars to, when there is one). Call `check_env_keys` on it first (it returns present/absent, never values — never read the file directly). -3. Call `set_env_values`, passing the secretRef as a value object, not a - literal string — e.g. - `values: { "POSTHOG_CLI_API_KEY": { secretRef: "" }, "POSTHOG_CLI_PROJECT_ID": "", "POSTHOG_CLI_HOST": "" }`. - The exact variable names follow the skill's per-uploader convention. The - wizard resolves the ref locally, so you never see the key value. +3. Settle the variable names before you write them — see "The names are a + contract" below. Then call `set_env_values`, passing the secretRef as a + value object, not a literal string — e.g. + `values: { "POSTHOG_CLI_API_KEY": { secretRef: "" }, "POSTHOG_CLI_PROJECT_ID": "", "POSTHOG_CLI_HOST": "" }` + with whichever names you settled on. The wizard resolves the ref locally, so + you never see the key value. 4. Document the same variable names for other developers: append them to `.env.example` (create it if the project has none) with empty or placeholder values — never a real value, and never the key itself. The @@ -48,6 +49,28 @@ write it in this one task (the key never survives across tasks): Replace ``, ``, and `` from your project context. Do not touch the build config — the `configure` task owns that. +## The names are a contract + +An env variable only works if the name in the file is the name the code reads. +You write the file; the `configure` task writes the code that reads it, and you +two run in parallel. So never invent a name that already exists somewhere else +— look for the other half of the contract first, and adopt it: + +- **Read the build config before you choose.** If it already references PostHog + env variables, use exactly those names, whatever they are, even when they are + not the ones your skill would suggest. Reading config is safe — it holds + names, not secrets. +- **Only when nothing references them yet** are you the one deciding. Use the + names your skill documents for the mechanism that was actually wired. +- **Either way, say which set you chose in your handoff, in full.** That is how + `configure` and `wire-ci` learn what to match. A handoff that says "the usual + variables" hands the next task the same guess you just made. + +Names that merely look plausible are the failure here. A build reading +`POSTHOG_API_KEY` beside an env file holding `POSTHOG_CLI_API_KEY` throws no +error anywhere — the upload simply never runs, and every stack trace stays +minified. + ## How you know you succeeded The env file holds the upload variables (the key as a resolved secret, the From 12bc01be7dbda93b7bb13fe169935321c3943749 Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Wed, 9 Sep 2026 15:17:34 +0200 Subject: [PATCH 14/35] feat(error-tracking): make init check the variable an existing init reads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A repo can carry a PostHog init whose env variable was never defined — the call is there, the name it reads is nowhere, and the client is built from an empty string. The init task read that as "already initialised" and left it alone, so the app captured nothing while every other step looked clean. Init now confirms the name the code reads is present with check_env_keys before it leaves an existing init alone, and writes it when it is absent. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01X5Kw3YahYz9RiFeJ8r2hxY --- context/agents/error-tracking/init.md | 36 +++++++++++++++++++++------ 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/context/agents/error-tracking/init.md b/context/agents/error-tracking/init.md index f1d4295f..3fa46edc 100644 --- a/context/agents/error-tracking/init.md +++ b/context/agents/error-tracking/init.md @@ -7,7 +7,7 @@ effort_pi: low model_sdk: claude-sonnet-4-6 effort_sdk: medium skills: [integration-v2-init, posthog-best-practices] -allowedTools: [Read, Write, Edit, Glob, Grep, Bash] +allowedTools: [Read, Write, Edit, Glob, Grep, Bash, check_env_keys, set_env_values] disallowedTools: [enqueue_task] dependsOn: [] --- @@ -25,6 +25,27 @@ repo without PostHog. Initialize the SDK so exceptions can flow and stop — no instrumentation, no extras. Don't set up exception capture either way; the capture-exceptions task after you owns that. +## An existing init still needs its variable defined + +"Already initialised" is a property of the pair, not of the call. An init point +that reads `process.env.SOMETHING` only works when `SOMETHING` is defined in the +env file the project loads. A repo can carry an init that has never once run: +the call is there, the variable it names is nowhere, and the client is built +from an empty string. + +So before you leave an existing init alone, call `check_env_keys` on that env +file and look for the exact name the init reads. It returns names, never values. + +- **Present** — the pair is complete. Leave it alone and say so. +- **Absent** — the init is not wired yet, whoever wrote it. Write that variable + with `set_env_values` under the name the code already reads, and document it + in `.env.example`. Do not rename the code to match a name you would rather + have written; the code is the half that already exists. + +An empty key is the quiet failure here. A client constructed from `''` throws +nothing and logs nothing. The build is clean, the app starts, every capture call +returns — and no event ever arrives. + ## Make the environment actually reachable Writing the keys to `.env` is only half the job — something has to load that @@ -71,9 +92,10 @@ publishable — it ships inside the browser bundle either way. ## How you know you succeeded An init point exists with the PostHog env keys present — whether it already -did or you just created it — keys in the env file, never hardcoded. On a -platform that does not auto-load `.env`, the loading is wired; on a platform -with no environment at all, the token is a literal rather than a lookup into -something that never defines it. Your handoff names the files involved, how the client is -constructed, and how `.env` reaches it, so the capture-exceptions task can find -the init options without re-discovering them. +did or you just created it — keys in the env file and confirmed there with +`check_env_keys`, never hardcoded. On a platform that does not auto-load +`.env`, the loading is wired; on a platform with no environment at all, the +token is a literal rather than a lookup into something that never defines it. +Your handoff names the files involved, how the client is constructed, and how +`.env` reaches it, so the capture-exceptions task can find the init options +without re-discovering them. From 4de201cadbc497f9cc1e2cb99200b08564f8760a Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Wed, 9 Sep 2026 15:23:19 +0200 Subject: [PATCH 15/35] feat(error-tracking): treat a keyless init as not integrated MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The seed read fact 1 as "the SDK is in the manifest", so a repo carrying an init whose key variable was defined nowhere skipped install and init entirely. The client was then built from an empty string: the build stayed clean, every capture call returned, and no event ever arrived. Fact 1 is now about the pair — the call and the key it reads. When the name is defined nowhere, or the seed cannot tell, it queues init, which re-checks the pair and leaves a complete init alone. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01X5Kw3YahYz9RiFeJ8r2hxY --- context/agents/error-tracking/init.md | 8 +++---- .../error-tracking/setup-error-tracking.md | 23 +++++++++++++++---- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/context/agents/error-tracking/init.md b/context/agents/error-tracking/init.md index 3fa46edc..0e8d96dd 100644 --- a/context/agents/error-tracking/init.md +++ b/context/agents/error-tracking/init.md @@ -20,10 +20,10 @@ leave it alone and say so in your handoff. If it doesn't, create it following your skill — it owns the how: the framework's init point, the env-var wiring through the wizard tools, and `.env.example`. -You only exist in this flow because the user asked for error tracking on a -repo without PostHog. Initialize the SDK so exceptions can flow and stop — -no instrumentation, no extras. Don't set up exception capture either way; -the capture-exceptions task after you owns that. +You exist in this flow because the user asked for error tracking on a repo +whose PostHog init is missing or unproven. Initialize the SDK so exceptions can +flow and stop — no instrumentation, no extras. Don't set up exception capture +either way; the capture-exceptions task after you owns that. ## An existing init still needs its variable defined diff --git a/context/agents/error-tracking/setup-error-tracking.md b/context/agents/error-tracking/setup-error-tracking.md index f7ac0ce4..168c247c 100644 --- a/context/agents/error-tracking/setup-error-tracking.md +++ b/context/agents/error-tracking/setup-error-tracking.md @@ -25,6 +25,17 @@ First establish two facts from the repo: the dependency manifests, or a `posthog.init(...)` / snippet in the source. Check the project state for existing events if the repo is ambiguous. +Integrated means the init runs, not that the package is listed. An init point +is a pair: the call, and the key it is constructed from. A repo can carry the +call while the key it names is defined nowhere — then the client is built from +an empty string and captures nothing, however complete the manifest looks. So +when you find an init that reads a variable, look for that same name in the +repo's committed env template or its build config. Found, or the project state +shows real events arriving: integrated. Named nowhere, or you cannot tell: +**queue `init`**. It re-checks the pair itself and leaves a complete init +alone, so queuing it when you are unsure costs one cheap task, while skipping +it on a keyless init costs the whole run. + **2. Which uploader variant is this project — or none?** Read the manifests and pick at most one, by this precedence (first match wins): @@ -63,10 +74,11 @@ readable-stack platforms listed above, and Astro. Then seed the graph: -- `install` and `init`, independent of each other — **only when PostHog is - not integrated**. Do not stop on an uninstrumented repo, integrate. -- `capture-exceptions`, after `install` and `init` (with no dependencies when - PostHog was already integrated). +- `install`, only when the SDK is missing from the manifest. +- `init`, independent of `install` — whenever fact 1 did not show a complete + pair. Do not stop on an uninstrumented repo, integrate. +- `capture-exceptions`, after whichever of `install` and `init` you queued + (with no dependencies when you queued neither). - When an uploader variant matched, add the upload subgraph: - `credentials`, no dependencies — it stops to ask the user for a personal API key, so keep it a root task: the prompt reaches the user early while @@ -89,7 +101,8 @@ Every task in the chosen graph is queued with that dependency shape, the four upload tasks (when queued) share the same `{ skillId, displayName }` inputs, `report` depends on the rest (directly or transitively), and the first task is runnable. Your plan states both facts explicitly: whether PostHog was -integrated, and which uploader variant matched — or, when you queue no upload +integrated — and, when you called it integrated, the name of the key you found +defined — and which uploader variant matched — or, when you queue no upload tasks, why no variant applies: which readable-stack platform this is, or that Astro is not supported by the uploader. A plan that never mentions fact 2 is an incomplete plan, not a decision. Keep From 7a4f0828dbdbb4d86c8c1a1b0f919868c1801e46 Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Wed, 9 Sep 2026 16:18:25 +0200 Subject: [PATCH 16/35] feat(error-tracking): keep the type assertion in a typed build config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A Next.js run copied the uploader example into next.config.ts without the non-null assertion the reference shows. process.env lookups are string | undefined, the plugin option is string, and the build failed type checking — after the upload step had already run, so the log showed a successful chunk upload and the build still exited 1. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01X5Kw3YahYz9RiFeJ8r2hxY --- context/agents/error-tracking/configure.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/context/agents/error-tracking/configure.md b/context/agents/error-tracking/configure.md index 3a559df6..3a6b35e0 100644 --- a/context/agents/error-tracking/configure.md +++ b/context/agents/error-tracking/configure.md @@ -49,6 +49,14 @@ whose `package.json` sets `"type": "module"` the bundle dies at boot with `ReferenceError: module is not defined in ES module scope`. Read the `type` field before writing the command. +Types are the same kind of trap. In a config the compiler checks — a `.ts` +config, or JS under `checkJs` — `process.env.ANYTHING` is `string | undefined`, +while a plugin's options usually require `string`. Where your skill's example +asserts or defaults that lookup, keep that part exactly: dropping a `!` or a +`?? ''` turns a working example into a build that fails type checking. The +upload step often runs before the type check, so the maps land and the build +still exits non-zero — a broken build that looks half-successful in the log. + ## The names are a contract An env variable only works if the name the code reads is the name in the file. From 779b9998feb2d28cd6ad4be7a61c38b5169356cc Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Wed, 9 Sep 2026 20:10:08 +0200 Subject: [PATCH 17/35] feat(error-tracking): forward the release identity across the CI boundary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit wire-ci was written entirely around credentials, and pointed at the skill's CI step while the git-context guidance sits in a different step the task never named. In a container build neither reaches the uploader: .git is in .dockerignore, and the CI's own variables stop at the docker build command. Reproduced against the nested composite action fixture: Release fields are incomplete and git info is unavailable (exit 1) Docker build args are not inherited from the environment, so this fails on a GitHub Actions runner exactly as it does locally — verified by building with GITHUB_SHA exported but not forwarded. The ARG-plus-ENV shape is scoped to the git variables and non-secret settings; the API key keeps its build-secret mount, which a first pass had demoted to a plain build arg. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01X5Kw3YahYz9RiFeJ8r2hxY --- context/agents/error-tracking/wire-ci.md | 28 ++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/context/agents/error-tracking/wire-ci.md b/context/agents/error-tracking/wire-ci.md index a0ac48f2..8ac80c3b 100644 --- a/context/agents/error-tracking/wire-ci.md +++ b/context/agents/error-tracking/wire-ci.md @@ -29,9 +29,33 @@ config that is not there. You cannot create the CI secret that holds the API key; reference it by name and carry that follow-up, plus any deploy path you could not trace, into your handoff for the report. +## Two things cross the boundary, not one + +Credentials are half of it. The uploader also needs a **release identity** — a +name and a version — and it derives that from the CI's own git variables or +from a `.git` directory. A container build sees neither: `.git` is almost +always in `.dockerignore`, and the CI's variables stop at the `docker build` +command. Nor does a hardcoded release name rescue you; the uploader wants both +halves, and stops the build when it has only one. + +So forward the provider's git variables into the build the same way you forward +credentials, and declare each one as `ARG` **and** `ENV` — `ARG` alone is not +visible to the uploader's environment lookup. Your skill's "Associate the +release with a git commit" step lists the variables per provider. Read that +step even though it is not the CI step: this boundary is where it applies. + +That `ARG`-plus-`ENV` shape is for the git variables and the non-secret settings +only. The API key is a secret and keeps whatever secret-carrying mechanism the +build system offers — a build secret mounted for the one step that needs it, a +masked variable, a secret file. A secret in `ARG` or `ENV` is recorded in the +build history, and the builder itself will warn you: *do not use ARG or ENV +instructions for sensitive data*. Widening the git-variable pattern to cover the +key is a downgrade, not consistency. + ## How you know you succeeded The pipeline that runs the production build carries the upload credentials by -the same names the credentials task used, and every secret the user still has -to create is named in your handoff. Your handoff lists the CI files you changed +the same names the credentials task used and reaches the uploader with a +resolvable release identity. Every secret the user still has to create is named +in your handoff. Your handoff lists the CI files you changed and every manual follow-up, so the report can hand them to the user. From 9983432c07664a1d9a2a35ee309deb179c63a94b Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Wed, 9 Sep 2026 20:26:40 +0200 Subject: [PATCH 18/35] feat(error-tracking): match the app's own env names at the deploy boundary wire-ci carried the upload credentials through the pipeline but nothing owned the variable the deploy step hands the running app. The step predates PostHog, so it passed the project's old name while init had settled on another, and the container booted with an undefined key. Reproduced by running the image exactly as the deploy step does: POSTHOG_PROJECT_TOKEN= undefined NODE_ENV= production exit=0 Nothing fails: in production the init guard returns rather than throwing, so the process serves traffic and reports nothing. The runtime boundary is now an explicit third place a name has to match, with the deploy step renamed to follow the code rather than the other way round. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01X5Kw3YahYz9RiFeJ8r2hxY --- context/agents/error-tracking/wire-ci.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/context/agents/error-tracking/wire-ci.md b/context/agents/error-tracking/wire-ci.md index 8ac80c3b..06c8c80e 100644 --- a/context/agents/error-tracking/wire-ci.md +++ b/context/agents/error-tracking/wire-ci.md @@ -52,10 +52,29 @@ build history, and the builder itself will warn you: *do not use ARG or ENV instructions for sensitive data*. Widening the git-variable pattern to cover the key is a downgrade, not consistency. +## The runtime boundary has names too + +Upload credentials are a build-time concern. The app also reads its own +variables at **run** time, and the step that starts it is another place a name +has to match — a `docker run -e`, a compose file, a systemd unit, a platform's +environment settings. That step was written before PostHog existed here, so the +name it passes is whatever the project used back then. If `init` settled on a +different one, the deployed process starts with an undefined key. + +Nothing fails loudly. In production the init guard returns quietly instead of +throwing, so the process boots, serves traffic, and reports nothing — the same +silent shape as a mismatched upload variable, one boundary later. + +So read the names the app's own source reads, and make the start step pass +exactly those. Rename the deploy step's variable, not the code: the code is the +half that already exists. Name any secret the user must create under its new +name in your handoff. + ## How you know you succeeded The pipeline that runs the production build carries the upload credentials by the same names the credentials task used and reaches the uploader with a -resolvable release identity. Every secret the user still has to create is named +resolvable release identity, and the step that starts the app passes the +variable names the app's own source reads. Every secret the user still has to create is named in your handoff. Your handoff lists the CI files you changed and every manual follow-up, so the report can hand them to the user. From c9f89e5947fed046427e6385683ef5ac80869a74 Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Wed, 9 Sep 2026 20:58:27 +0200 Subject: [PATCH 19/35] fix(error-tracking): forward CI git vars only where a provider sets them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The release-identity rule was written as if a pipeline always exists, so a project whose Dockerfile says it is built and run by hand got eight GITHUB_* build args nothing ever fills. Empty variables resolve to no release, and a hand-run docker build failed: Oops! Release fields are incomplete and git info is unavailable (exit 1) Reading as wired while failing the build is the worst of both. With no provider the identity is supplied outright instead — the re-run took --release-version from the manifest, and the hand-run build resolves cicd-docker-node-raw@0.1.0. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01X5Kw3YahYz9RiFeJ8r2hxY --- context/agents/error-tracking/wire-ci.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/context/agents/error-tracking/wire-ci.md b/context/agents/error-tracking/wire-ci.md index 06c8c80e..981c690d 100644 --- a/context/agents/error-tracking/wire-ci.md +++ b/context/agents/error-tracking/wire-ci.md @@ -44,6 +44,15 @@ visible to the uploader's environment lookup. Your skill's "Associate the release with a git commit" step lists the variables per provider. Read that step even though it is not the CI step: this boundary is where it applies. +Forward them only where a provider actually sets them. A project with no +pipeline — an image built and run by hand — has nothing to inherit from, and +variables declared but never filled resolve to no release at all: it reads as +wired and still fails the build. There the identity has to be supplied outright, +both halves, from something the build itself holds — a build argument the +operator passes, or the manifest's own version — so name and version are always +present. Decide which case you are in by reading the repo, not by assuming a +provider exists. + That `ARG`-plus-`ENV` shape is for the git variables and the non-secret settings only. The API key is a secret and keeps whatever secret-carrying mechanism the build system offers — a build secret mounted for the one step that needs it, a From 010ff33d3793be39aad3e8adaf153915ac0d3a32 Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Thu, 10 Sep 2026 10:57:10 +0200 Subject: [PATCH 20/35] feat(error-tracking): make the run script load the env file it depends on The Go run wrote credentials to .env, rewrote main.go to read them from the process env, added a Makefile run target that loads nothing, and then pointed the user at that target to verify. It prints its own "variable missing" guard and exits: build green, symbols uploaded, no event. The run-script rule now covers reaching the values as well as running the right artifact. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01X5Kw3YahYz9RiFeJ8r2hxY --- context/agents/error-tracking/configure.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/context/agents/error-tracking/configure.md b/context/agents/error-tracking/configure.md index 3a6b35e0..4cbbf3bc 100644 --- a/context/agents/error-tracking/configure.md +++ b/context/agents/error-tracking/configure.md @@ -42,6 +42,14 @@ When your changes make the build emit a bundle to a new directory (`dist/`, never executes the bundle the maps were uploaded for, so every uploaded map goes unused. Add or fix the script that serves the built output. +A run script also has to reach the values the app reads. When the credentials +live in an env file and the platform does not load it on its own, the script you +add or fix has to load it — export it before the binary, pass the runtime's own +flag for it, whatever that platform offers. Skip that and the artifact starts, +prints its own "variable missing" guard, and reports nothing: the build is +green, the symbols are uploaded, and the one command you told the user to run is +the one that cannot capture. + Match the bundle's module format to the package's type while you write the command — you cannot run it, so it has to be right by construction. `esbuild --platform=node` emits CommonJS unless you pass `--format=esm`, so in a package From ee6f8f262fd4c8908d83f069d068ff14517b46d8 Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Thu, 10 Sep 2026 12:50:21 +0200 Subject: [PATCH 21/35] feat(error-tracking): give the capture step its own docs matrix error-tracking-step borrowed the integration group's variants, so the task that wires exception capture read the SDK library page. For Go and Rust those pages say nothing about panic hooks or log handlers, and the task invented the API: a capture_panics method on the wrong builder, and a slog handler with no distinct-ID resolver, which drops every capture without erroring. The matrix is unchanged - same 40 ids, frameworks and defaults, verified against the built menu before and after. Only docs_urls move: 39 variants now point at their error-tracking installation page, java being the one PostHog has none for. Where the page is a generic substitute (django to python, astro to web) the framework's own library page stays alongside. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01X5Kw3YahYz9RiFeJ8r2hxY --- context/agents/error-tracking/configure.md | 19 +- .../error-tracking-step/config.yaml | 427 +++++++++++++++++- 2 files changed, 437 insertions(+), 9 deletions(-) diff --git a/context/agents/error-tracking/configure.md b/context/agents/error-tracking/configure.md index 4cbbf3bc..878995c0 100644 --- a/context/agents/error-tracking/configure.md +++ b/context/agents/error-tracking/configure.md @@ -42,13 +42,18 @@ When your changes make the build emit a bundle to a new directory (`dist/`, never executes the bundle the maps were uploaded for, so every uploaded map goes unused. Add or fix the script that serves the built output. -A run script also has to reach the values the app reads. When the credentials -live in an env file and the platform does not load it on its own, the script you -add or fix has to load it — export it before the binary, pass the runtime's own -flag for it, whatever that platform offers. Skip that and the artifact starts, -prints its own "variable missing" guard, and reports nothing: the build is -green, the symbols are uploaded, and the one command you told the user to run is -the one that cannot capture. +That run script has its own environment to satisfy, and it is not the build's. +The uploader's credentials are handled — your build step passes them itself. The +separate question is the variables **the application reads when it starts**: the +project token and host its init looks up. Those live in the same gitignored env +file, and a compiled binary or a bare interpreter loads nothing on its own. + +So open the init point, note the variables it reads, and make the run script +provide exactly those — export the env file ahead of the command, pass the +runtime's own flag for it, whatever that platform offers. Skip it and the +artifact starts, prints its own "variable missing" guard, and reports nothing: +the build is green, the symbols are uploaded, and the single command you hand +the user to verify with is the one command that cannot capture. Match the bundle's module format to the package's type while you write the command — you cannot run it, so it has to be right by construction. `esbuild diff --git a/context/skills/integration-v2/error-tracking-step/config.yaml b/context/skills/integration-v2/error-tracking-step/config.yaml index 89c15127..8d84a58a 100644 --- a/context/skills/integration-v2/error-tracking-step/config.yaml +++ b/context/skills/integration-v2/error-tracking-step/config.yaml @@ -1,10 +1,433 @@ # Orchestrator step-skill: fetched by task agents, never surfaced as a command. -# The framework matrix is borrowed from the canonical integration group. +# The framework matrix mirrors the canonical integration group's ids, but the +# docs are its own: integration teaches SDK setup, this step teaches exception +# capture, and for several platforms those are different pages. Borrowing the +# matrix would hand the capture task the library page and nothing about panic +# hooks, log handlers, or framework error boundaries. type: docs-only template: description.md description: Capture exceptions with PostHog around critical flows tags: [orchestrator, error-tracking] cli: role: internal -variants_from: integration +variants: +- id: nextjs-app-router + framework: nextjs + default: true + display_name: Next.js App Router + tags: + - nextjs + - react + - ssr + - app-router + - javascript + - javascript_web + - javascript_node + docs_urls: + - https://posthog.com/docs/error-tracking/installation/nextjs.md +- id: nextjs-pages-router + framework: nextjs + display_name: Next.js Pages Router + tags: + - nextjs + - react + - ssr + - pages-router + - javascript + - javascript_web + - javascript_node + docs_urls: + - https://posthog.com/docs/error-tracking/installation/nextjs.md +- id: react-react-router-6 + framework: react-router + default: true + display_name: React Router v6 + tags: + - react + - react-router + - v6 + - spa + - javascript + - javascript_web + docs_urls: + - https://posthog.com/docs/error-tracking/installation/react.md +- id: react-react-router-7-framework + framework: react-router + display_name: React Router v7 - Framework mode + tags: + - react + - react-router + - v7 + - framework + - ssr + - javascript + - javascript_node + - javascript_web + docs_urls: + - https://posthog.com/docs/error-tracking/installation/react.md +- id: react-react-router-7-data + framework: react-router + display_name: React Router v7 - Data mode + tags: + - react + - react-router + - v7 + - data + - spa + - javascript + - javascript_web + docs_urls: + - https://posthog.com/docs/error-tracking/installation/react.md +- id: react-react-router-7-declarative + framework: react-router + display_name: React Router v7 - Declarative mode + tags: + - react + - react-router + - v7 + - declarative + - spa + - javascript + - javascript_web + docs_urls: + - https://posthog.com/docs/error-tracking/installation/react.md +- id: react-vite + display_name: React (Vite) + tags: + - react + - vite + - spa + - javascript + - javascript_web + docs_urls: + - https://posthog.com/docs/error-tracking/installation/react.md +- id: nuxt-3-6 + framework: nuxt + default: true + display_name: Nuxt 3.6 + tags: + - nuxt + - javascript + - javascript_web + - javascript_node + docs_urls: + - https://posthog.com/docs/error-tracking/installation/nuxt-3-6.md +- id: nuxt-4 + framework: nuxt + display_name: Nuxt 4 + tags: + - nuxt + - vue + - ssr + - javascript + - typescript + - javascript_web + - javascript_node + docs_urls: + - https://posthog.com/docs/error-tracking/installation/nuxt-3-7.md +- id: vue-3 + framework: vue + display_name: Vue 3 + tags: + - vue + - javascript + - typescript + - javascript_web + docs_urls: + - https://posthog.com/docs/error-tracking/installation/web.md + - https://posthog.com/docs/libraries/vue-js.md +- id: django + framework: django + display_name: Django + tags: + - django + - python + docs_urls: + - https://posthog.com/docs/error-tracking/installation/python.md + - https://posthog.com/docs/libraries/django.md +- id: flask + framework: flask + display_name: Flask + tags: + - flask + - python + docs_urls: + - https://posthog.com/docs/error-tracking/installation/python.md + - https://posthog.com/docs/libraries/flask.md +- id: fastapi + framework: fastapi + display_name: FastAPI + tags: + - fastapi + - python + docs_urls: + - https://posthog.com/docs/error-tracking/installation/python.md + - https://posthog.com/docs/libraries/python.md +- id: react-tanstack-router-file-based + framework: tanstack-router + display_name: React with TanStack Router (file-based) + tags: + - react + - tanstack-router + - spa + - javascript + - javascript_web + docs_urls: + - https://posthog.com/docs/error-tracking/installation/react.md + - https://posthog.com/docs/libraries/tanstack-start.md +- id: react-tanstack-router-code-based + framework: tanstack-router + default: true + display_name: React with TanStack Router (code-based) + tags: + - react + - tanstack-router + - spa + - javascript + - javascript_web + docs_urls: + - https://posthog.com/docs/error-tracking/installation/react.md + - https://posthog.com/docs/libraries/tanstack-start.md +- id: tanstack-start + framework: tanstack-start + display_name: TanStack Start + tags: + - react + - tanstack-start + - tanstack-router + - javascript + - javascript_web + - javascript_node + docs_urls: + - https://posthog.com/docs/error-tracking/installation/react.md + - https://posthog.com/docs/libraries/tanstack-start.md +- id: laravel + framework: laravel + display_name: Laravel + tags: + - laravel + - php + docs_urls: + - https://posthog.com/docs/error-tracking/installation/php.md + - https://posthog.com/docs/libraries/laravel.md +- id: php + display_name: PHP + tags: + - php + docs_urls: + - https://posthog.com/docs/error-tracking/installation/php.md +- id: wordpress + framework: wordpress + display_name: WordPress + tags: + - wordpress + - php + - cms + docs_urls: + - https://posthog.com/docs/error-tracking/installation/php.md + - https://posthog.com/docs/libraries/wordpress.md + - https://posthog.com/docs/libraries/php.md +- id: ruby-on-rails + framework: rails + display_name: Ruby on Rails + tags: + - ruby-on-rails + - ruby + docs_urls: + - https://posthog.com/docs/error-tracking/installation/ruby-on-rails.md +- id: android + framework: android + display_name: Android + tags: + - android + - java + - kotlin + docs_urls: + - https://posthog.com/docs/error-tracking/installation/android.md +- id: sveltekit + framework: sveltekit + display_name: SvelteKit + tags: + - sveltekit + - svelte + - javascript + - javascript_web + - javascript_node + docs_urls: + - https://posthog.com/docs/error-tracking/installation/svelte.md + - https://posthog.com/docs/libraries/svelte.md +- id: python + framework: python + display_name: Python + tags: + - python + docs_urls: + - https://posthog.com/docs/error-tracking/installation/python.md +- id: javascript_node + framework: javascript_node + display_name: JavaScript Node + tags: + - javascript_node + - javascript + docs_urls: + - https://posthog.com/docs/error-tracking/installation/node.md +- id: javascript_web + framework: javascript_web + display_name: JavaScript Web + tags: + - javascript_web + - javascript + docs_urls: + - https://posthog.com/docs/error-tracking/installation/web.md +- id: ruby + framework: ruby + display_name: Ruby + tags: + - ruby + docs_urls: + - https://posthog.com/docs/error-tracking/installation/ruby.md +- id: elixir + framework: elixir + display_name: Elixir + tags: + - elixir + - phoenix + - plug + docs_urls: + - https://posthog.com/docs/error-tracking/installation/elixir.md +- id: java + framework: java + display_name: Java (Spring Boot) + tags: + - java + - java-server + docs_urls: + - https://posthog.com/docs/libraries/java.md +- id: go + framework: go + display_name: Go + tags: + - go + docs_urls: + - https://posthog.com/docs/error-tracking/installation/go.md +- id: rust + framework: rust + display_name: Rust + tags: + - rust + docs_urls: + - https://posthog.com/docs/error-tracking/installation/rust.md +- id: swift + framework: swift + display_name: Swift (iOS/macOS) + tags: + - swift + - ios + - macos + - swiftui + - xcodegen + docs_urls: + - https://posthog.com/docs/error-tracking/installation/ios.md + - https://posthog.com/docs/libraries/ios.md + - https://posthog.com/docs/libraries/ios/usage.md + - https://posthog.com/docs/libraries/ios/configuration.md +- id: flutter + framework: flutter + display_name: Flutter + tags: + - flutter + - dart + - mobile + docs_urls: + - https://posthog.com/docs/error-tracking/installation/flutter.md +- id: kmp + display_name: Kotlin Multiplatform + tags: + - kmp + - kotlin + - multiplatform + - mobile + docs_urls: + - https://posthog.com/docs/error-tracking/installation/kmp.md +- id: react-native + framework: react-native + default: true + display_name: React Native + tags: + - react-native + - javascript + - typescript + docs_urls: + - https://posthog.com/docs/error-tracking/installation/react-native.md +- id: expo + framework: react-native + display_name: Expo + tags: + - expo + - react-native + - javascript + - typescript + docs_urls: + - https://posthog.com/docs/error-tracking/installation/react-native.md + - https://posthog.com/docs/libraries/react-native.md +- id: astro-static + framework: astro + display_name: Astro (Static) + tags: + - astro + - javascript + - typescript + - javascript_web + docs_urls: + - https://posthog.com/docs/error-tracking/installation/web.md + - https://posthog.com/docs/libraries/astro.md +- id: astro-view-transitions + framework: astro + display_name: Astro (View Transitions) + tags: + - astro + - astro-view-transitions + - javascript + - typescript + - javascript_web + docs_urls: + - https://posthog.com/docs/error-tracking/installation/web.md + - https://posthog.com/docs/libraries/astro.md +- id: astro-ssr + framework: astro + display_name: Astro (SSR) + tags: + - astro + - astro-ssr + - javascript + - typescript + - javascript_web + - javascript_node + docs_urls: + - https://posthog.com/docs/error-tracking/installation/web.md + - https://posthog.com/docs/libraries/astro.md +- id: astro-hybrid + framework: astro + default: true + display_name: Astro (Hybrid) + tags: + - astro + - astro-hybrid + - javascript + - typescript + - javascript_web + - javascript_node + docs_urls: + - https://posthog.com/docs/error-tracking/installation/web.md + - https://posthog.com/docs/libraries/astro.md +- id: angular + framework: angular + display_name: Angular + tags: + - angular + - javascript + - typescript + - javascript_web + docs_urls: + - https://posthog.com/docs/error-tracking/installation/angular.md packaging: bundle From a98931c74d538e416158c3e5ac2c40d573270268 Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Thu, 10 Sep 2026 13:01:39 +0200 Subject: [PATCH 22/35] fix(error-tracking): stop the capture task splicing two SDK setups The Rust page shows a global initializer and an explicit client side by side. The task took the wiring from one and the calls from the other, binding init_global's Result<(), Error> as if it were a client, so cargo build failed with three E0599s. It also wrote the build as a !-prefixed cargo alias, which is a git convention cargo has no support for. Six lines: follow one setup the whole way through, and put a multi-step build where the tool actually runs it. The re-run compiles, uploads symbols, and a panic resolves to rust_app::kaboom_rust2 -> read_rust_retry_budget2. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01X5Kw3YahYz9RiFeJ8r2hxY --- context/agents/error-tracking/capture-exceptions.md | 5 +++++ context/agents/error-tracking/configure.md | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/context/agents/error-tracking/capture-exceptions.md b/context/agents/error-tracking/capture-exceptions.md index 5c14e6d5..3d4a1f47 100644 --- a/context/agents/error-tracking/capture-exceptions.md +++ b/context/agents/error-tracking/capture-exceptions.md @@ -21,6 +21,11 @@ error handler, some give you a boundary to mount at the app entry. Follow the docs and the reference example for this one, and set it up in one place — never manual capture calls sprinkled across files. +A page often shows two setups side by side: a global initializer, and an +explicit client you hold. Follow one of them the whole way through. Taking the +wiring from one and the calls from the other reads fine and compiles nowhere — +an initializer that hands back nothing leaves you nothing to call methods on. + The SDK is installed and initialized — either it already was, or the install and init tasks before you did it (see their handoffs); build on that, do not re-check it. diff --git a/context/agents/error-tracking/configure.md b/context/agents/error-tracking/configure.md index 878995c0..4aeb4c85 100644 --- a/context/agents/error-tracking/configure.md +++ b/context/agents/error-tracking/configure.md @@ -55,6 +55,10 @@ artifact starts, prints its own "variable missing" guard, and reports nothing: the build is green, the symbols are uploaded, and the single command you hand the user to verify with is the one command that cannot capture. +Put a multi-step build somewhere the tool actually runs it — a script file, a +make target, the manifest's own scripts — never an alias mechanism you are +assuming exists, borrowed from a neighbouring tool. + Match the bundle's module format to the package's type while you write the command — you cannot run it, so it has to be right by construction. `esbuild --platform=node` emits CommonJS unless you pass `--format=esm`, so in a package From 567bd0413a0e8feb5506e56f1d0bcb382bec12ed Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Fri, 11 Sep 2026 17:20:19 +0200 Subject: [PATCH 23/35] fix(error-tracking): default Nuxt to the 3.7+ capture docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The capture step's Nuxt matrix marked `nuxt-3-6` as the default, so every Nuxt project — including Nuxt 4 — was handed the v3.0-3.6 installation page and wired PostHog by hand through a client plugin. Nuxt 3.7 shipped in September 2023; the `@posthog/nuxt` module is the documented path for everything since, and it initialises both the Vue and Nitro sides itself. Moves `default: true` to the `nuxt-4` variant, which already pointed at installation/nuxt-3-7.md. Two lines change in the built menu and nothing else. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01X5Kw3YahYz9RiFeJ8r2hxY --- context/skills/integration-v2/error-tracking-step/config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/context/skills/integration-v2/error-tracking-step/config.yaml b/context/skills/integration-v2/error-tracking-step/config.yaml index 8d84a58a..cad9c778 100644 --- a/context/skills/integration-v2/error-tracking-step/config.yaml +++ b/context/skills/integration-v2/error-tracking-step/config.yaml @@ -103,7 +103,6 @@ variants: - https://posthog.com/docs/error-tracking/installation/react.md - id: nuxt-3-6 framework: nuxt - default: true display_name: Nuxt 3.6 tags: - nuxt @@ -114,6 +113,7 @@ variants: - https://posthog.com/docs/error-tracking/installation/nuxt-3-6.md - id: nuxt-4 framework: nuxt + default: true display_name: Nuxt 4 tags: - nuxt From a146290c4e38bf0fc46553df38af5cd3dc41dfeb Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Sun, 13 Sep 2026 16:00:56 +0200 Subject: [PATCH 24/35] fix(error-tracking): keep the capture task off the SDK's own listeners MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Exception autocapture already registers the global handlers — window.onerror and unhandledrejection in the browser, uncaughtException and unhandledRejection in Node, sys.excepthook in Python, the panic hook in Rust, the uncaught exception and signal handlers on iOS and Android. A second listener the task writes itself on the same event sends every error twice and drifts from the SDK's handling as the SDK changes. The capture task now turns autocapture on through the SDK's option and registers none of those handlers. Framework hooks stay in scope, because they see errors a framework swallows before any global handler fires: Express error middleware, Fastify setErrorHandler, Hono onError, Vue errorHandler, Angular ErrorHandler, SvelteKit handleError, a React error boundary, Next.js global-error. Go, which has no autocapture, keeps its entry-point boundary. The success check gains the same condition, so a reviewer can confirm it by reading the diff. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_013SEsZQQrYjXPVEhQuoSwE6 --- .../error-tracking/capture-exceptions.md | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/context/agents/error-tracking/capture-exceptions.md b/context/agents/error-tracking/capture-exceptions.md index 3d4a1f47..7ffbc73d 100644 --- a/context/agents/error-tracking/capture-exceptions.md +++ b/context/agents/error-tracking/capture-exceptions.md @@ -37,10 +37,32 @@ either way; when the flow includes a configure task, it owns those files. Stay inside this project's directory and set up that one place; that is the whole job. +## Do not copy the SDK's own listeners + +Exception autocapture already registers the global handlers: `window.onerror` +and `unhandledrejection` in the browser, `uncaughtException` and +`unhandledRejection` in Node, `sys.excepthook` in Python, the panic hook in +Rust, and the uncaught-exception and signal handlers on iOS and Android. Turn +that on with the SDK's own option — `capture_exceptions`, +`enableExceptionAutocapture`, `enable_exception_autocapture`, `capture_panics`, +`errorTrackingConfig.autoCapture` — and register none of those handlers +yourself. A second listener on the same event sends every error twice, and it +stops matching the SDK's handling the moment the SDK changes. + +What the SDK cannot see is yours to add: errors a framework catches before any +global handler fires. Express error middleware, Fastify `setErrorHandler`, Hono +`onError`, Vue `app.config.errorHandler`, Angular `ErrorHandler`, SvelteKit +`handleError`, a React error boundary, Next.js `global-error` — hook those, +because the framework swallows the error and the global listener never hears +of it. A platform with no autocapture at all, such as Go, is the other case: +there the capture boundary at the entry point is the mechanism, not a copy of +one. + ## How you know you succeeded An error the app does not catch reaches PostHog, through the mechanism this -SDK gives you rather than one you invented. You did not install anything, run +SDK gives you rather than one you invented, and no global error listener of +your own sits beside the SDK's autocapture. You did not install anything, run a build, lint, or tests, search outside the project, or read through the whole app or hand-wrap individual components or routes. Your handoff names the files you changed and the capture mechanism, so the report can explain it to the From 3927abad80af613f55495559469ac16a9a8f1225 Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Mon, 14 Sep 2026 11:30:15 +0200 Subject: [PATCH 25/35] test(skills): check error-tracking-step covers every integration variant error-tracking-step declares its variants literally, and the orchestrator resolves it per framework with no fallback. A variant added to integration and not copied here would abort the default orchestrator flow for that framework. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_013SEsZQQrYjXPVEhQuoSwE6 --- .../tests/error-tracking-step-parity.test.js | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 scripts/lib/tests/error-tracking-step-parity.test.js diff --git a/scripts/lib/tests/error-tracking-step-parity.test.js b/scripts/lib/tests/error-tracking-step-parity.test.js new file mode 100644 index 00000000..8ec49a1b --- /dev/null +++ b/scripts/lib/tests/error-tracking-step-parity.test.js @@ -0,0 +1,21 @@ +import { describe, expect, it } from 'vitest'; +import { join } from 'path'; + +import { loadSkillsConfig } from '../skill-generator.js'; + +const CONFIG_DIR = join(process.cwd(), 'context'); + +// error-tracking-step declares its variants literally (its docs differ from +// integration's), but the orchestrator resolves it per framework with no +// fallback. A variant added to integration and not copied here makes the +// default orchestrator flow abort for that framework. +describe('integration-v2/error-tracking-step', () => { + it('declares every integration variant, with the same framework', () => { + const config = loadSkillsConfig(CONFIG_DIR); + const keys = (group) => config[group].variants.map((v) => `${v.id}:${v.framework}`); + + expect(keys('integration-v2/error-tracking-step')).toEqual( + expect.arrayContaining(keys('integration')), + ); + }); +}); From 1700aaecf0c3b528092cf1699f0b9a5eefb70109 Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Mon, 14 Sep 2026 11:30:15 +0200 Subject: [PATCH 26/35] fix(agents): use allowed wizard models in the error-tracking flow - model_sdk: claude-sonnet-5 and claude-haiku-4-5, as #389 did for the other flows. The old ids are no longer valid in the wizard. - install and init run terra at low effort, like integration-v2 and replay-vision. wire-ci runs sol at medium. The gateway mint pins sol to medium. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_013SEsZQQrYjXPVEhQuoSwE6 --- context/agents/error-tracking/capture-exceptions.md | 2 +- context/agents/error-tracking/configure.md | 2 +- context/agents/error-tracking/credentials.md | 2 +- context/agents/error-tracking/init.md | 4 ++-- context/agents/error-tracking/install.md | 4 ++-- context/agents/error-tracking/report.md | 2 +- context/agents/error-tracking/setup-error-tracking.md | 2 +- context/agents/error-tracking/test-setup.md | 2 +- context/agents/error-tracking/wire-ci.md | 4 ++-- 9 files changed, 12 insertions(+), 12 deletions(-) diff --git a/context/agents/error-tracking/capture-exceptions.md b/context/agents/error-tracking/capture-exceptions.md index 7ffbc73d..d1f9da58 100644 --- a/context/agents/error-tracking/capture-exceptions.md +++ b/context/agents/error-tracking/capture-exceptions.md @@ -4,7 +4,7 @@ flow: error-tracking label: Wire up exception capture model_pi: openai/gpt-5.6-sol effort_pi: medium -model_sdk: claude-sonnet-4-6 +model_sdk: claude-sonnet-5 effort_sdk: high skills: [integration-v2-error-tracking-step, posthog-best-practices] allowedTools: [Read, Write, Edit, Glob, Grep] diff --git a/context/agents/error-tracking/configure.md b/context/agents/error-tracking/configure.md index 4aeb4c85..b39a0ab2 100644 --- a/context/agents/error-tracking/configure.md +++ b/context/agents/error-tracking/configure.md @@ -4,7 +4,7 @@ flow: error-tracking label: Apply build-config changes model_pi: openai/gpt-5.6-sol effort_pi: medium -model_sdk: claude-sonnet-4-6 +model_sdk: claude-sonnet-5 effort_sdk: medium skills: [] allowedTools: [Read, Write, Edit, Glob, Grep, Bash, load_skill_menu, install_skill, check_env_keys] diff --git a/context/agents/error-tracking/credentials.md b/context/agents/error-tracking/credentials.md index ae86250d..b09ebb63 100644 --- a/context/agents/error-tracking/credentials.md +++ b/context/agents/error-tracking/credentials.md @@ -4,7 +4,7 @@ flow: error-tracking label: Get and write the upload credentials model_pi: openai/gpt-5.6-sol effort_pi: medium -model_sdk: claude-sonnet-4-6 +model_sdk: claude-sonnet-5 effort_sdk: medium skills: [] allowedTools: [Read, Write, Edit, Glob, Grep, Bash, load_skill_menu, install_skill, wizard_ask] diff --git a/context/agents/error-tracking/init.md b/context/agents/error-tracking/init.md index 0e8d96dd..49595a04 100644 --- a/context/agents/error-tracking/init.md +++ b/context/agents/error-tracking/init.md @@ -2,9 +2,9 @@ type: init flow: error-tracking label: Set up PostHog initialization -model_pi: openai/gpt-5.6-sol +model_pi: openai/gpt-5.6-terra effort_pi: low -model_sdk: claude-sonnet-4-6 +model_sdk: claude-sonnet-5 effort_sdk: medium skills: [integration-v2-init, posthog-best-practices] allowedTools: [Read, Write, Edit, Glob, Grep, Bash, check_env_keys, set_env_values] diff --git a/context/agents/error-tracking/install.md b/context/agents/error-tracking/install.md index 14a2e7ff..92099a8f 100644 --- a/context/agents/error-tracking/install.md +++ b/context/agents/error-tracking/install.md @@ -2,9 +2,9 @@ type: install flow: error-tracking label: Add the PostHog SDK to the manifest -model_pi: openai/gpt-5.6-sol +model_pi: openai/gpt-5.6-terra effort_pi: low -model_sdk: claude-haiku-4-5-20251001 +model_sdk: claude-haiku-4-5 skills: [integration-v2-install] allowedTools: [Read, Edit, Glob, Grep, Bash] disallowedTools: [enqueue_task] diff --git a/context/agents/error-tracking/report.md b/context/agents/error-tracking/report.md index 366611a0..aa267fba 100644 --- a/context/agents/error-tracking/report.md +++ b/context/agents/error-tracking/report.md @@ -5,7 +5,7 @@ label: Summarise and hand off sink: true model_pi: openai/gpt-5.6-luna effort_pi: low -model_sdk: claude-sonnet-4-6 +model_sdk: claude-sonnet-5 effort_sdk: medium skills: [] allowedTools: [Read, Glob, Grep, Write, posthog_exec] diff --git a/context/agents/error-tracking/setup-error-tracking.md b/context/agents/error-tracking/setup-error-tracking.md index 168c247c..cde10e70 100644 --- a/context/agents/error-tracking/setup-error-tracking.md +++ b/context/agents/error-tracking/setup-error-tracking.md @@ -4,7 +4,7 @@ flow: error-tracking seed: true model_pi: openai/gpt-5.6-terra effort_pi: medium -model_sdk: claude-sonnet-4-6 +model_sdk: claude-sonnet-5 effort_sdk: high skills: [] allowedTools: [Read, Glob, Grep, posthog_exec] diff --git a/context/agents/error-tracking/test-setup.md b/context/agents/error-tracking/test-setup.md index b5d6dfb2..b9f058ce 100644 --- a/context/agents/error-tracking/test-setup.md +++ b/context/agents/error-tracking/test-setup.md @@ -4,7 +4,7 @@ flow: error-tracking label: Offer to test the local setup model_pi: openai/gpt-5.6-sol effort_pi: medium -model_sdk: claude-sonnet-4-6 +model_sdk: claude-sonnet-5 effort_sdk: high skills: [] allowedTools: [Read, Write, Edit, Glob, Grep, Bash, load_skill_menu, install_skill, wizard_ask] diff --git a/context/agents/error-tracking/wire-ci.md b/context/agents/error-tracking/wire-ci.md index 981c690d..958a6fe6 100644 --- a/context/agents/error-tracking/wire-ci.md +++ b/context/agents/error-tracking/wire-ci.md @@ -3,8 +3,8 @@ type: wire-ci flow: error-tracking label: Set up CI for automatic uploads model_pi: openai/gpt-5.6-sol -effort_pi: high -model_sdk: claude-sonnet-4-6 +effort_pi: medium +model_sdk: claude-sonnet-5 effort_sdk: high skills: [] allowedTools: [Read, Write, Edit, Glob, Grep, load_skill_menu, install_skill] From 488e7595244c69194a14f67efe7187c0459a3cc9 Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Mon, 14 Sep 2026 13:56:21 +0200 Subject: [PATCH 27/35] fix(agents): pick the react-native uploader before native markers A React Native repo carries ios/Podfile, an .xcodeproj and Gradle files, so the seed's native-first precedence picked the ios uploader and wired no JavaScript source maps. react-native or expo in package.json now wins over every native marker. Verified with e2e runs on the react-native fixture (ios before, react-native after) and on react-native-expo. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01668rN31F4aotxmenNksz4Z --- context/agents/error-tracking/setup-error-tracking.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/context/agents/error-tracking/setup-error-tracking.md b/context/agents/error-tracking/setup-error-tracking.md index cde10e70..80b21d7b 100644 --- a/context/agents/error-tracking/setup-error-tracking.md +++ b/context/agents/error-tracking/setup-error-tracking.md @@ -39,6 +39,11 @@ it on a keyless init costs the whole run. **2. Which uploader variant is this project — or none?** Read the manifests and pick at most one, by this precedence (first match wins): +- `react-native` or `expo` in `package.json` dependencies → `react-native`. + A React Native repo also carries `ios/Podfile`, an `.xcodeproj` and Gradle + files for its native shells, so this rule wins over every native marker + below. Picking `ios` or `android` for it uploads no JavaScript source maps, + and every JavaScript stack trace stays minified. - `pubspec.yaml` → `flutter` - an `.xcodeproj`, `Podfile`, or `Package.swift` → `ios` - a Gradle build file (`build.gradle`, `build.gradle.kts`, `settings.gradle`) → `android` @@ -50,7 +55,7 @@ and pick at most one, by this precedence (first match wins): then fails the whole build. This rule wins over every `package.json` match below — an Astro project that also depends on `vite` is still **none**. - otherwise read `package.json` dependencies, first match wins: - `react-native` → `react-native`; `nuxt` → `nuxt`; `next` → `nextjs`; + `nuxt` → `nuxt`; `next` → `nextjs`; `@angular/core` → `angular`; `vite` → `vite`; `webpack` → `webpack`; `rollup` → `rollup`; `react` → `react`; server-only Node → `node`; any other browser JS → `web` From 136a9dd447a4c322cfc64fe8f9464a26714d8a1f Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Mon, 14 Sep 2026 14:10:23 +0200 Subject: [PATCH 28/35] fix(agents): make the error-tracking init type-check In a type-checked project the init guard tested a separate variable and then passed the unchecked one to the SDK, so tsc stopped the build with TS2345 and no source maps uploaded. The init now passes the variable it checked, or keeps an existing ?? '' / ! as it is. Verified with e2e runs on node-webpack and node-raw: no type errors, and node-raw builds, uploads and starts. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01668rN31F4aotxmenNksz4Z --- context/agents/error-tracking/init.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/context/agents/error-tracking/init.md b/context/agents/error-tracking/init.md index 49595a04..1a2c4ce7 100644 --- a/context/agents/error-tracking/init.md +++ b/context/agents/error-tracking/init.md @@ -88,6 +88,20 @@ token as a literal in the committed `src/environments/*` files. This is the skill's "no valid environment to read from" case, and the public token is publishable — it ships inside the browser bundle either way. +## The init has to type-check + +You cannot run the build either. So in a project the compiler checks — +TypeScript, or JavaScript under `checkJs` — the init has to compile by +construction. `process.env.ANYTHING` is `string | undefined`, and the SDK +constructor wants a `string`. + +Pass the variable that you checked. `if (!key) { … } else { new PostHog(key, …) }` +narrows `key` to `string`. A check on a different variable does not: store the +missing name in `missing`, test `missing`, then pass `key`, and the compiler +still sees `string | undefined`. The build then stops with "Argument of type +'string | undefined' is not assignable to parameter of type 'string'", emits +no bundle, and uploads no source maps. When the existing code already passes +`process.env.KEY ?? ''` or `process.env.KEY!`, keep that part as it is. ## How you know you succeeded @@ -96,6 +110,8 @@ did or you just created it — keys in the env file and confirmed there with `check_env_keys`, never hardcoded. On a platform that does not auto-load `.env`, the loading is wired; on a platform with no environment at all, the token is a literal rather than a lookup into something that never defines it. +In a type-checked project, the value you pass to the SDK is narrowed to a +`string`. Your handoff names the files involved, how the client is constructed, and how `.env` reaches it, so the capture-exceptions task can find the init options without re-discovering them. From 811a5e921dbb7f3410764fc9100341333b8531d7 Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Mon, 14 Sep 2026 15:42:29 +0200 Subject: [PATCH 29/35] fix(skills): do not set a devtool beside the PostHog webpack plugin With sourcemaps.enabled the webpack plugin adds its own SourceMapDevToolPlugin. A devtool that also emits maps makes webpack fail with "Conflict: Multiple assets emit different content to the same filename index.js.map", and nothing uploads. Verified with an e2e run on node-webpack: no devtool, the build passes, one chunk uploads, and the app starts. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01668rN31F4aotxmenNksz4Z --- context/skills/error-tracking-upload-source-maps/description.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/context/skills/error-tracking-upload-source-maps/description.md b/context/skills/error-tracking-upload-source-maps/description.md index 90078145..37249760 100644 --- a/context/skills/error-tracking-upload-source-maps/description.md +++ b/context/skills/error-tracking-upload-source-maps/description.md @@ -38,7 +38,7 @@ Wire source map generation, chunk-ID injection, and upload into your **productio #### Examples - **Node / tsc** Emit maps with embedded sources by setting both in `tsconfig.json`: `"sourceMap": true` and `"inlineSources": true`. Add the CLI to the project with `npm install --save-dev @posthog/cli@latest` (or the project's package manager) so the build script and CI resolve the same binary — never hand-write the version string. Then run `posthog-cli sourcemap process` against the build output dir as a post-build step — it injects chunk IDs and uploads in one pass, and needs the upload credentials (see "Make credentials available at build time"). -- **Vite / Webpack / Rollup** Prefer the bundler plugin from the reference over hand-rolling the CLI — it injects and uploads in one pass. Make sure the bundler is configured to emit source maps. +- **Vite / Webpack / Rollup** Prefer the bundler plugin from the reference over hand-rolling the CLI — it injects and uploads in one pass. For Vite and Rollup, make sure the bundler is configured to emit source maps. **Webpack is the exception:** with `sourcemaps.enabled`, the PostHog webpack plugin adds its own `SourceMapDevToolPlugin`, so do not also set a `devtool` that emits maps. Remove `devtool: 'source-map'` or `'hidden-source-map'`, or set `devtool: false`. When both emit `[file].map`, the build fails with `Conflict: Multiple assets emit different content to the same filename index.js.map` and nothing uploads. - **iOS (Xcode)** iOS uploads **dSYM debug symbols**, not source maps. Required target changes: 1. `DEBUG_INFORMATION_FORMAT = dwarf-with-dsym` for Release. 2. `ENABLE_USER_SCRIPT_SANDBOXING = NO`. From 843bd1e38effb28fefe825edf605cf66dcc310aa Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Mon, 14 Sep 2026 16:21:41 +0200 Subject: [PATCH 30/35] fix(agents): stop turning on Error Tracking from the report task The flow relies on SDK autocapture and does not change team settings. Remove the products-enable step and posthog_exec from the report task. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01668rN31F4aotxmenNksz4Z --- context/agents/error-tracking/report.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/context/agents/error-tracking/report.md b/context/agents/error-tracking/report.md index aa267fba..d4ee2086 100644 --- a/context/agents/error-tracking/report.md +++ b/context/agents/error-tracking/report.md @@ -8,7 +8,7 @@ effort_pi: low model_sdk: claude-sonnet-5 effort_sdk: medium skills: [] -allowedTools: [Read, Glob, Grep, Write, posthog_exec] +allowedTools: [Read, Glob, Grep, Write] disallowedTools: [enqueue_task] dependsOn: [capture-exceptions, wire-ci, test-setup] --- @@ -21,11 +21,6 @@ each task's report — the capture mechanism, the files changed, the env variable names, the CI secret to create, and any deploy path that could not be traced. Do not re-derive any of it from the project. -First, turn on the Error Tracking product for the team (`products-enable` -through `posthog_exec`) so the captured exceptions have a UI to land in. If -the call fails or the tool is missing, carry it as a follow-up — never fail -the report over it. - Write the hand-off to `posthog-error-tracking-report.md` at the top level of this project's directory. When the run wired source-map upload, START it with a **"What you still need to do"** section — numbered, copy-pasteable: From 476c40f61312140a6d12aa5fed3f60052107e18c Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Mon, 14 Sep 2026 17:06:51 +0200 Subject: [PATCH 31/35] fix(agents): pick the Android uploader only for Android Gradle projects A Gradle file alone matched `android`, so a Gradle JVM server got the Android upload tasks. Require an Android Gradle plugin or an AndroidManifest.xml. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01668rN31F4aotxmenNksz4Z --- context/agents/error-tracking/setup-error-tracking.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/context/agents/error-tracking/setup-error-tracking.md b/context/agents/error-tracking/setup-error-tracking.md index 80b21d7b..88b8abbd 100644 --- a/context/agents/error-tracking/setup-error-tracking.md +++ b/context/agents/error-tracking/setup-error-tracking.md @@ -46,7 +46,9 @@ and pick at most one, by this precedence (first match wins): and every JavaScript stack trace stays minified. - `pubspec.yaml` → `flutter` - an `.xcodeproj`, `Podfile`, or `Package.swift` → `ios` -- a Gradle build file (`build.gradle`, `build.gradle.kts`, `settings.gradle`) → `android` +- a Gradle build file that applies `com.android.application` or + `com.android.library`, or an `AndroidManifest.xml` → `android`. A Gradle + project without these markers is a JVM server: keep reading. - `go.mod` → `go` - `Cargo.toml` → `rust` - `astro` in `package.json` dependencies → **none**. Astro is not supported by From e98bb0ec783445a66204713f88abe8c1da5d6be8 Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Tue, 15 Sep 2026 16:29:38 +0200 Subject: [PATCH 32/35] chore(agents): run error-tracking install and init at medium effort Review feedback: low effort is too little for the variety of real projects these two tasks meet. The gateway already admits terra at medium. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01668rN31F4aotxmenNksz4Z --- context/agents/error-tracking/init.md | 2 +- context/agents/error-tracking/install.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/context/agents/error-tracking/init.md b/context/agents/error-tracking/init.md index 1a2c4ce7..d3059ba7 100644 --- a/context/agents/error-tracking/init.md +++ b/context/agents/error-tracking/init.md @@ -3,7 +3,7 @@ type: init flow: error-tracking label: Set up PostHog initialization model_pi: openai/gpt-5.6-terra -effort_pi: low +effort_pi: medium model_sdk: claude-sonnet-5 effort_sdk: medium skills: [integration-v2-init, posthog-best-practices] diff --git a/context/agents/error-tracking/install.md b/context/agents/error-tracking/install.md index 92099a8f..8ddb6527 100644 --- a/context/agents/error-tracking/install.md +++ b/context/agents/error-tracking/install.md @@ -3,7 +3,7 @@ type: install flow: error-tracking label: Add the PostHog SDK to the manifest model_pi: openai/gpt-5.6-terra -effort_pi: low +effort_pi: medium model_sdk: claude-haiku-4-5 skills: [integration-v2-install] allowedTools: [Read, Edit, Glob, Grep, Bash] From fa69873e0d0a84ab42c14a25df28b34ea77f7fa1 Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Tue, 15 Sep 2026 16:36:31 +0200 Subject: [PATCH 33/35] fix(agents): write the error-tracking report before the optional test Review feedback: test-setup ran before the report, so a user who stepped away waited on its question before the report was written. The report is now a plain task and test-setup is the sink after it. The question says setup is done, the report is written, and the test is optional. No answer counts as a decline, and a failed test is added to the report. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01668rN31F4aotxmenNksz4Z --- context/agents/error-tracking/report.md | 3 +- .../error-tracking/setup-error-tracking.md | 15 ++++--- context/agents/error-tracking/test-setup.md | 43 ++++++++++++------- 3 files changed, 37 insertions(+), 24 deletions(-) diff --git a/context/agents/error-tracking/report.md b/context/agents/error-tracking/report.md index d4ee2086..0125e99d 100644 --- a/context/agents/error-tracking/report.md +++ b/context/agents/error-tracking/report.md @@ -2,7 +2,6 @@ type: report flow: error-tracking label: Summarise and hand off -sink: true model_pi: openai/gpt-5.6-luna effort_pi: low model_sdk: claude-sonnet-5 @@ -10,7 +9,7 @@ effort_sdk: medium skills: [] allowedTools: [Read, Glob, Grep, Write] disallowedTools: [enqueue_task] -dependsOn: [capture-exceptions, wire-ci, test-setup] +dependsOn: [capture-exceptions, wire-ci] --- ## Goal diff --git a/context/agents/error-tracking/setup-error-tracking.md b/context/agents/error-tracking/setup-error-tracking.md index 88b8abbd..bfce880d 100644 --- a/context/agents/error-tracking/setup-error-tracking.md +++ b/context/agents/error-tracking/setup-error-tracking.md @@ -93,10 +93,12 @@ Then seed the graph: - `configure`, after `capture-exceptions` — build-config changes; it runs after the code edits so the two never fight over the same files. - `wire-ci`, after `configure` and `credentials`. - - `test-setup`, after `wire-ci` — offers the user a local end-to-end test - last, once everything is wired. -- `report`, after every other queued task. It writes the handoff last, so it - describes what actually shipped. +- `report`, after every other queued task except `test-setup`. It writes the + handoff once the work is done, so it describes what actually shipped. +- When you queued the upload subgraph, `test-setup` last, after `report` — it + offers the user an optional local end-to-end test. The report comes first on + purpose: a user who walks away still gets it. Queue `report` before + `test-setup`, because `test-setup` names `report` as its dependency. Never plan an identify, capture, dashboard, or session-replay task — this run sets up error tracking, not the full integration. The minimal SDK footprint @@ -106,8 +108,9 @@ that `install` and `init` leave behind is enough for exceptions to flow. Every task in the chosen graph is queued with that dependency shape, the four upload tasks (when queued) share the same `{ skillId, displayName }` inputs, -`report` depends on the rest (directly or transitively), and the first task is -runnable. Your plan states both facts explicitly: whether PostHog was +`report` depends on every other task except `test-setup` (directly or +transitively), `test-setup` (when queued) depends on `report`, and the first +task is runnable. Your plan states both facts explicitly: whether PostHog was integrated — and, when you called it integrated, the name of the key you found defined — and which uploader variant matched — or, when you queue no upload tasks, why no variant applies: which readable-stack platform this is, or that diff --git a/context/agents/error-tracking/test-setup.md b/context/agents/error-tracking/test-setup.md index b9f058ce..505f434a 100644 --- a/context/agents/error-tracking/test-setup.md +++ b/context/agents/error-tracking/test-setup.md @@ -1,7 +1,8 @@ --- type: test-setup flow: error-tracking -label: Offer to test the local setup +label: Offer an optional local test +sink: true model_pi: openai/gpt-5.6-sol effort_pi: medium model_sdk: claude-sonnet-5 @@ -9,33 +10,43 @@ effort_sdk: high skills: [] allowedTools: [Read, Write, Edit, Glob, Grep, Bash, load_skill_menu, install_skill, wizard_ask] disallowedTools: [enqueue_task] -dependsOn: [wire-ci] +dependsOn: [report] --- ## Goal -Offer the user a one-time, end-to-end check that errors reach PostHog with -readable stack traces. Install the skill your task input names (`install_skill` -with the `skillId`) and follow its **"Test the local setup"** step for the -platform-appropriate affordance, the `captureException` shape, the placement, -and the read-before-edit / always-revert rules. +The setup is done and the report is written. Offer the user an optional, +one-time, end-to-end check that errors reach PostHog with readable stack traces. +Install the skill your task input names (`install_skill` with the `skillId`) and +follow its **"Test the local setup"** step for the platform-appropriate +affordance, the `captureException` shape, the placement, and the +read-before-edit / always-revert rules. + +You run after the report on purpose. A user who stepped away still gets a +finished setup and a report, so nothing waits on this test. First ask with `wizard_ask`: -`{ id: "test-affordance", prompt: "Want me to help you test your local setup? I'll add a temporary test button (or route) to your app so you can confirm errors show up in Error Tracking with readable stack traces after your next build. I'll remove it once you've confirmed it works.", kind: "single", options: [{ label: "Yes, help me test it", value: "yes" }, { label: "No, I'll test on my own later", value: "no" }] }` +`{ id: "test-affordance", prompt: "Error tracking is set up, and the report is in posthog-error-tracking-report.md. Optional: want me to help you test it locally? I'll add a temporary test button (or route) so you can confirm errors show up in Error Tracking with readable stack traces after your next build, then remove it.", kind: "single", options: [{ label: "Yes, help me test it", value: "yes" }, { label: "No, I'm done", value: "no" }] }` -- **"no"** (or `wizard_ask` unavailable): do nothing to the code and report this - task done, noting the test was offered and declined. +- **"no"**, no answer (`__cancelled__`), or `wizard_ask` unavailable: do nothing + to the code and report this task done, noting the test was offered and not + taken. - **"yes"**: add the affordance per the skill, then pause with a second `wizard_ask` (id `"test-done"`, a single `Continue (revert test code)` option) whose prompt gives the build, run, and Error-Tracking-check as literal numbered steps (build first — it uploads the maps — then trigger the affordance, then confirm the error resolves to real source in Error - Tracking). After the user continues, REVERT every test edit per the skill's - rules. Never leave the affordance in place, even if the user says it didn't - work — revert first, then carry the failure into your handoff. + Tracking). After the user continues, or when that ask comes back unanswered, + REVERT every test edit per the skill's rules. Never leave the affordance in + place, even if the user says it didn't work. + +When the user says the test failed, add a short **"Local test"** section at the +end of `posthog-error-tracking-report.md` with what they saw, and say the same +in chat. The report task has already run, so this is the only place the +failure is written down. ## How you know you succeeded -Either the user declined and no code changed, or the affordance was added, -tested, and fully reverted. Your handoff says which, and carries any failure -the user reported for the report to surface. +Either the offer was declined or unanswered and no code changed, or the +affordance was added, tested, and fully reverted. Your handoff says which, and +a failure the user reported is in the report file. From 0886e3b6c98f39e55702eb8f4e67f853c16bfe1a Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Tue, 15 Sep 2026 17:03:21 +0200 Subject: [PATCH 34/35] fix(agents): carry a missing posthog-cli into the error-tracking report Review feedback: the wizard's pre-install warning is a status line that scrolls away. The configure task now checks that a machine-global posthog-cli is on the PATH when its uploader needs one, and names the install as a follow-up in its handoff. The report lists it under what the user still has to do. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01668rN31F4aotxmenNksz4Z --- context/agents/error-tracking/configure.md | 8 ++++++++ context/agents/error-tracking/report.md | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/context/agents/error-tracking/configure.md b/context/agents/error-tracking/configure.md index b39a0ab2..6c435550 100644 --- a/context/agents/error-tracking/configure.md +++ b/context/agents/error-tracking/configure.md @@ -36,6 +36,14 @@ translating any `npm install` the skill or docs show (`pnpm add -D …`, `workspace:*` dependencies (`EUNSUPPORTEDPROTOCOL`) — that error means the wrong manager, never a flag to retry with. +When the upload step you wire runs a machine-global `posthog-cli` rather than +one from the project's own dependencies, check that it is on the `PATH` +(`command -v posthog-cli`). Do not try to install it: a global install is +blocked for you, and the wizard already tried before your task. When it is +missing, name it in your handoff as a manual follow-up — run +`npm install -g @posthog/cli@latest` before the next release build — because +without it the upload step cannot run. + When your changes make the build emit a bundle to a new directory (`dist/`, `build/`), check that some script actually runs that output. A project whose `build` writes `dist/index.js` while `start` still runs the original source diff --git a/context/agents/error-tracking/report.md b/context/agents/error-tracking/report.md index 0125e99d..83fb18f2 100644 --- a/context/agents/error-tracking/report.md +++ b/context/agents/error-tracking/report.md @@ -30,7 +30,7 @@ a **"What you still need to do"** section — numbered, copy-pasteable: 2. Add it as the CI secret the wire-ci step referenced, named exactly as in the pipeline config. 3. Any other manual follow-up the handoffs carry (an untraceable deploy path, - provider-side settings). + provider-side settings, installing a missing `posthog-cli`). Then cover, briefly and concretely: From ba65456c35bb11d4dd8867e948332e45e790325b Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Tue, 15 Sep 2026 18:07:53 +0200 Subject: [PATCH 35/35] fix(warlock): triage through the gateway's ci product The llm-gateway retired its legacy wizard product (PostHog/posthog#96861), which now refuses every caller with a 403. Every triage call failed, so each Warlock match defaulted to a true positive and the build blocked. The ci product serves CI runs with a personal API key and allows all models. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01668rN31F4aotxmenNksz4Z --- scripts/scan-warlock.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/scripts/scan-warlock.js b/scripts/scan-warlock.js index 1015ff78..adddb94f 100644 --- a/scripts/scan-warlock.js +++ b/scripts/scan-warlock.js @@ -68,10 +68,11 @@ const isCI = Boolean(process.env.CI); // matches. The LLM decides whether each match is a real threat or a // false positive // -// Gateway URL pattern matches the wizard: -// US: https://gateway.us.posthog.com/wizard -// EU: https://gateway.eu.posthog.com/wizard -// Local: http://localhost:3308/wizard +// The gateway's `ci` product serves CI runs with a personal API key. The +// legacy `wizard` product is retired and refuses every caller with a 403. +// US: https://gateway.us.posthog.com/ci +// EU: https://gateway.eu.posthog.com/ci +// Local: http://localhost:3308/ci function getGatewayUrl() { const host = process.env.POSTHOG_HOST || "https://us.posthog.com"; @@ -88,12 +89,12 @@ function getGatewayUrl() { } if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1") { - return "http://localhost:3308/wizard"; + return "http://localhost:3308/ci"; } if (hostname === "eu.posthog.com" || hostname === "eu.i.posthog.com") { - return "https://gateway.eu.posthog.com/wizard"; + return "https://gateway.eu.posthog.com/ci"; } - return "https://gateway.us.posthog.com/wizard"; + return "https://gateway.us.posthog.com/ci"; } function createLLMProvider() {