From 9308cfe379b7415048e568daecce2098c54c4247 Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Tue, 1 Sep 2026 18:03:50 +0200 Subject: [PATCH 01/19] 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 76889ed7fe91463fc225a7eb745308ebc1255738 Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Tue, 1 Sep 2026 18:21:22 +0200 Subject: [PATCH 02/19] 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 1b8e1230184bc9db9832ff8dc7a63c468c729600 Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Wed, 2 Sep 2026 10:49:09 +0200 Subject: [PATCH 03/19] 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 06a49a4813016ad4ebb2e0d5821e3f427d8dd273 Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Wed, 2 Sep 2026 10:55:43 +0200 Subject: [PATCH 04/19] 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 f31e15fae0a1efeeb79b865e9f3d6d414a45491d Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Thu, 3 Sep 2026 11:12:07 +0200 Subject: [PATCH 05/19] 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 09ba5c68adb0542b5c5498c3352ab9f02971ec9f Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Thu, 3 Sep 2026 12:47:03 +0200 Subject: [PATCH 06/19] 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 d50503f322498f95bbcacd9b6b793d87baeb0b8f Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Tue, 8 Sep 2026 19:31:22 +0200 Subject: [PATCH 07/19] 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 aa72cc1de166f521aad585cf5047eb86aa281f31 Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Tue, 8 Sep 2026 19:53:26 +0200 Subject: [PATCH 08/19] 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 14bf73b9bf31dbbb453f9c422c8832eb932633bf Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Tue, 8 Sep 2026 20:19:43 +0200 Subject: [PATCH 09/19] 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 9085c0d9e2649d48aa5e5c5ea1b71500b141f643 Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Tue, 8 Sep 2026 20:42:27 +0200 Subject: [PATCH 10/19] 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 3096836010652bb2de03267587412b1211c7dfe3 Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Tue, 8 Sep 2026 20:42:44 +0200 Subject: [PATCH 11/19] 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 d3ae5980c299551d51ce263d2bbd663f7cc18728 Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Wed, 9 Sep 2026 10:38:52 +0200 Subject: [PATCH 12/19] 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 e758d472ded016b515c452cbe987106f01189cd5 Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Wed, 9 Sep 2026 11:32:28 +0200 Subject: [PATCH 13/19] 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 2b78b415cd00398b4688e0bad3986d5f79c989f5 Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Wed, 9 Sep 2026 15:17:34 +0200 Subject: [PATCH 14/19] 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 978b6a2ca5763c3b99ed57f154e362e23bb40181 Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Wed, 9 Sep 2026 15:23:19 +0200 Subject: [PATCH 15/19] 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 445e49db9721cff4e9f7ba4b3dcc1dd35a7a79c1 Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Wed, 9 Sep 2026 16:18:25 +0200 Subject: [PATCH 16/19] 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 ad713bb9811e88fde027d6f285442935e8733fae Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Wed, 9 Sep 2026 20:10:08 +0200 Subject: [PATCH 17/19] 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 7a38c8ea3b41334d1316783a9d8e2d527f08c6c8 Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Wed, 9 Sep 2026 20:26:40 +0200 Subject: [PATCH 18/19] 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 41018db97b63159c5ad0efcdc09264b3f01fc516 Mon Sep 17 00:00:00 2001 From: ablaszkiewicz Date: Wed, 9 Sep 2026 20:58:27 +0200 Subject: [PATCH 19/19] 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