feat(expo): anonymous usage telemetry and skill feedback#71
Conversation
Adds an opt-out, anonymous PostHog telemetry layer plus a feedback path so we can see which Expo skills get used and improve them. - Shared, zero-dependency Node/Bun scripts in skills/skill-feedback/scripts (telemetry_common, skill-event, skill-feedback, telemetry toggle), launched via run.sh which picks node or bun and no-ops if neither is present. - Plugin-level Read hook (hooks/hooks.json) emits skill_read; a per-skill PostToolUse hook on all 16 skills emits skill_activated. Both are Claude Code only and scoped to this plugin. - Every skill gains a harness-agnostic "Expo Skill Feedback" footer for manual skill_feedback; a new skill-feedback skill documents the whole system. - Anonymous (hashed local install id; no PII, code, or paths). Opt-out via `telemetry.js --off`, EXPO_SKILLS_TELEMETRY=0, or DO_NOT_TRACK. Fully inert until a PostHog project key is configured. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses PR review feedback: - skill-event/skill-feedback now auto-detect the agent harness from env (CLAUDECODE -> claude-code; CODEX_SANDBOX / AGENT=codex -> codex; else unknown), removing the hardcoded `--agent-harness claude-code` from hooks.json, all 16 per-skill hooks, and every feedback footer. - skill-event derives the plugin root from its own location, so the Read hook no longer needs the `--plugin-root` flag. - Trim the CLAUDE.md telemetry section to just what it does and how to turn it off (full details live in the skill-feedback skill). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adopts proven patterns from entireio/cli: - Skip telemetry in CI (CI=true plus GitHub/GitLab/CircleCI/Travis/Buildkite/ Jenkins/TeamCity/Azure signals) so usage reflects real humans; surfaced in `telemetry.js --status`. - Add non-PII `os` / `arch` to every event. - Print a one-time "anonymous telemetry" notice to stderr on the first real send, gated by ~/.expo-skills/notice-shown. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…red) Skills load through Claude Code's Skill tool, not the Read tool — confirmed by inspecting the binary (skill_invoke / activeSkill, no read path) and a real `claude -p` run where invoking expo-observe fired the Skill tool with skill="expo:expo-observe" and zero Read calls. The old skill_read (PostToolUse:Read) hook therefore never fired on real usage. - Repoint the plugin hook to matcher "Skill" / event skill_invoked; read the skill name from tool_input.skill (strip the "plugin:" namespace) and scope to this plugin's own skills (<root>/skills/<name>/SKILL.md must exist). - Keep per-skill skill_activated (verified it fires). - Update docs (skill-feedback SKILL.md, README). Verified end-to-end: a real claude -p invoking expo-observe landed both skill_invoked and skill_activated in PostHog project 375452. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Track skill usage from both invocation paths, tagged with an `initiator`: - AI / model: PostToolUse `Skill` hook -> initiator=ai - user `/slash`: new UserPromptExpansion hook (reads command_name) -> initiator=user Verified against the binary + real `claude -p`: model invocation goes through the Skill tool (tool_input.skill), while user slash commands fire UserPromptExpansion with command_name (plugin skills namespaced "expo:expo-observe", stripped to the folder name). Both scoped to this plugin's skills; non-Expo slashes (e.g. /clear) are ignored. Adds `properties.initiator` to skill_invoked. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Now that skill_invoked (AI Skill tool + user UserPromptExpansion, tagged with
`initiator`) fully covers usage, drop the redundant machinery:
- Remove the `skill_activated` event and the per-skill `PostToolUse` frontmatter
hooks from all 16 skills (+ skill-feedback) — they fired at the same moment as
skill_invoked and added no distinct signal.
- Drop `model_config` (always "unknown") and `--context`/`parseContext` (unused).
- skill-event.js loses the activation dedup markers; one plugin-level hooks.json
does all auto-tracking; skills now carry only the feedback footer.
Two events remain — skill_invoked {skill, initiator, install-hash, agent_harness,
session-hash, os/arch} and skill_feedback — which answer: most-used skills,
AI-vs-user, unique installs, frequency/retention, and quality. Verified both
paths dry-run correctly and `claude plugin validate` passes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
main converted CLAUDE.md into a symlink -> AGENTS.md (the new instructions home) and added Codex/Cursor plugin + marketplace metadata. Resolved the CLAUDE.md "distinct types" conflict by keeping main's symlink and moving the "Usage Telemetry & Feedback" contributor note from CLAUDE.md into AGENTS.md. README auto-merged (keeps both the new Codex/Cursor install sections and the telemetry section). Telemetry code (hooks.json + skill-feedback/scripts) is unchanged; plugin and marketplace both validate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Codex now loads plugin hooks (openai/codex#17331 is closed) and exposes a PLUGIN_ROOT env var (≈ CLAUDE_PLUGIN_ROOT), but it still has no skill-invocation event: no Skill tool, UserPromptExpansion unimplemented, and skill hooks are on the parity roadmap (openai/codex#21753). So our matchers have nothing to bind to in Codex yet. - Update skill-feedback "Harness support" to this accurate state and document the minimal future enablement (a Codex hooks.json entry using ${PLUGIN_ROOT} + --initiator, plus one skillFromHook field). - Mark the skill-name extraction extension point in skill-event.js. Scripts are already harness-agnostic; no behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…observable Verified against codex-cli 0.130.0 + openai/codex main + a real `codex exec` run with live hooks: none of Codex's 10 hook events are skill-aware and no payload carries the skill name (skills are injected as <skill> context, not a tool; the SkillInvoked fact is internal analytics, never exposed to the hooks crate). Plugin-bundled hooks are also off by default (plugin_hooks, under development). Codex does alias CLAUDE_PLUGIN_ROOT, so our paths already resolve — enabling Codex is a tiny add once it ships a skill hook event + stable plugin_hooks. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Build it so Codex lights up automatically once it reaches hook parity (openai/codex#21753) + enables plugin_hooks — with no rework from us: - skillFromHook() now reads the skill name from any plausible payload field (tool_input.skill / tool_input.skill_name / command_name / top-level skill / skill_name). Strict skillBelongsToPlugin() scoping keeps this safe even though it's permissive (non-skills are dropped). - We already use Claude's hook event names (Skill, UserPromptExpansion) and ${CLAUDE_PLUGIN_ROOT}, which Codex mirrors/aliases — so the existing hooks.json fires in a parity Codex unchanged. Claude paths verified unchanged; non-skill payloads still skipped; plugin validates. Codex still can't fire yet (no skill-aware hook event; plugin_hooks off by default) — but our side needs zero further change for the parity path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Non-functional hardening from the multi-lens code review — no behavior
change to the happy path:
- skill-event.js: strip a leading "/" from slash-command skill payloads
before namespacing; trim the AI-path hook timeout 5s -> 3s.
- skill-feedback.js: fix $insert_id prefix "skill-feedback:" -> "skill_feedback:"
to match the event name; drop the unused maybeShowFirstRunNotice import/call
(feedback is explicit consent, so the first-run notice was misleading here).
- hooks.json: pass --plugin-root "${CLAUDE_PLUGIN_ROOT}" explicitly on both
hooks so plugin-scoping never depends on path inference.
- docs/comments: correct run.sh usage line, skill-feedback SKILL.md example
paths (${CLAUDE_SKILL_DIR}/scripts/...) and Codex example (use-dom),
telemetry_common stableStringify comment, and AGENTS.md opt-out guidance.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| Only ever use a *project* key (`phc_...`) here. Never a *personal* API key (`phx_...`) — | ||
| those are secret and used only for reading/querying (e.g. the PostHog MCP). | ||
|
|
||
| ## PostHog event shape |
There was a problem hiding this comment.
moved to /references
| If this skill was useful, confusing, broken, or missing context, submit 1-3 safe sentences with the bundled feedback script: | ||
|
|
||
| - **Claude Code:** `sh "${CLAUDE_SKILL_DIR}/../skill-feedback/scripts/run.sh" "${CLAUDE_SKILL_DIR}/../skill-feedback/scripts/skill-feedback.js" --skill upgrading-expo --rating idea --text "..."` | ||
| - **Other agents (Codex, etc.):** run `skill-feedback/scripts/skill-feedback.js` (bundled in this plugin) with `node` or `bun`, same flags (the harness is auto-detected). |
There was a problem hiding this comment.
then this sh script can be reused here too.
There was a problem hiding this comment.
i dropped the other agents, we'll know only support claude code :/ codex hooks don't support the stuff we need yet...
Kudo
left a comment
There was a problem hiding this comment.
the main concern is windows which doesn't have sh support by default.
| // per environment with EXPO_SKILLS_POSTHOG_KEY (e.g. a staging project). | ||
| // NOTE: never put a PostHog *personal* API key (phx_...) here — those are secret. | ||
| const POSTHOG_PROJECT_API_KEY = | ||
| process.env.EXPO_SKILLS_POSTHOG_KEY || "phc_w8xRytdAAwkV3oExnuUozqH64PMzCmDLnyoChpPBcNXs"; |
There was a problem hiding this comment.
[🏗️ design] Because a real phc_... key is committed, telemetryConfigured() is true, so telemetry is on by default for every install — not "inert until a key is configured" as the PR description says. The only runtime disclosure is the one-time stderr line in maybeShowFirstRunNotice(), and it fires from a --quiet PostToolUse hook whose stderr Claude Code hides by default (transcript/debug only). So in practice the first send is silent.
The anonymization, opt-out, DO_NOT_TRACK, and CI-skip are all solid. The open question is consent: is silent on-by-default the intended model, or should first run surface something the user actually sees? Either way, please drop the "fully inert until a key is configured" line from the PR body, since a key is committed.
There was a problem hiding this comment.
You were right to push on this — we ended up going further than a louder disclosure: telemetry is now opt-in and off by default (061f807). A fresh install sends nothing and creates nothing (not even the local install id) until the user enables it — telemetry.cjs --on, EXPO_SKILLS_TELEMETRY=1, or just telling the agent "enable Expo skills telemetry"; DO_NOT_TRACK/explicit-off/CI always win. Verified with a network interceptor on a simulated fresh install: zero outbound attempts while off, exactly one POST to PostHog once enabled. The stale "fully inert until a key is configured" line is gone — the PR description now describes the opt-in model.
|
|
||
| maybeShowFirstRunNotice(); | ||
| try { | ||
| await sendToPosthog(payload, { userAgent: "expo-skills/skill-event", timeoutMs: 3000 }); |
There was a problem hiding this comment.
[💡 suggestion] The file header says this "never blocks," but awaiting the POST keeps the hook process alive until PostHog responds or the 3s timeout fires — so each AI skill invocation can add up to 3s of latency to the turn (the hook waits for the process). For the quiet/auto path, consider detaching the send so telemetry stays off the critical path — e.g. background it in run.sh (node "$script" "$@" &) for the event scripts. Keep await in skill-feedback.js, since that path reports success to the user.
There was a problem hiding this comment.
Done in 772d40a — and in Node rather than run.sh & so it stays cross-platform: the hook process reads the payload, resolves the skill, runs the cheap local gates, then re-launches itself detached (spawn(..., {detached: true, stdio: 'ignore', windowsHide: true}).unref()) to do the POST, returning immediately — the turn no longer waits on the network. skill-feedback.cjs keeps the foreground await since it reports success to the user, exactly as you suggested. This also let us delete all three .sh wrappers (−67 lines of shell).
| "hooks": [ | ||
| { | ||
| "type": "command", | ||
| "command": "sh \"${CLAUDE_PLUGIN_ROOT}/skills/skill-feedback/scripts/run.sh\" \"${CLAUDE_PLUGIN_ROOT}/skills/skill-feedback/scripts/skill-event.js\" --skill auto --initiator ai --plugin-root \"${CLAUDE_PLUGIN_ROOT}\" --quiet", |
There was a problem hiding this comment.
note that sh doesn't support on windows
There was a problem hiding this comment.
Fixed in 772d40a — the sh wrappers are gone entirely. The hook (and the skill's documented commands) now invoke node directly, which runs the same under cmd, PowerShell, and POSIX shells, so Windows works without Git Bash. All the real logic was already zero-dependency Node; the shell layer was just launcher glue, now folded into skill-event.cjs itself.
|
|
||
| ## Turning it off | ||
|
|
||
| Telemetry is anonymous and on by default. The reliable, launch-independent way to opt |
There was a problem hiding this comment.
correct me if i am wrong. the skill-feedback skill is for agents without hook support to explicit evaluate the skill and share feedback.
in the case "on by default" or opt-out feels unclear to me if agent is going to explicitly send feedback
There was a problem hiding this comment.
This got resolved by the opt-in pivot (061f807): there is no on-by-default anything anymore. Both signals — the automatic skill_invoked hook event and the explicit skill_feedback — sit behind the same opt-in gate, so even an agent explicitly sending feedback sends nothing unless the user enabled telemetry; when it's off, the script refuses and the skill tells the agent to ask the user once (and never to enable it on its own). We also dropped the feedback footers from all product skills — feedback now lives only in the expo-skill-feedback skill.
| skill, | ||
| rating: args.rating, | ||
| feedback_text: feedback, | ||
| }, |
There was a problem hiding this comment.
not sure if feasible: i think relevant prompts (or chat history) that triggers the skill would be helpful to further study how to improve the skill
There was a problem hiding this comment.
Considered it, but decided to keep prompts/chat history out on purpose — it's the bright line of the privacy model (the skill promises never to send prompts, code, or paths), and raw transcripts can't be reliably scrubbed of secrets/PII. The substitute is the --text note: the agent writes 1–3 scrubbed sentences of qualitative context (what was confusing or missing), which keeps the 'why' for improving the skill without shipping anyone's conversation. If we need richer signal later, I'd add structured opt-in fields rather than raw history.
Brings in the expo-ui skill consolidation (#80), the openai-curated agent definitions (#74), the plugin-version-bump check (#79), README banner/metadata (#76, #82), and the main plugin version bump to 1.2.0 (#78). Conflict resolutions: - plugins/expo/.claude-plugin/plugin.json: version -> 1.3.0. main moved to 1.2.0, and the new plugin-version-bump check requires each manifest to be strictly greater than main, so this feature branch takes 1.3.0. Bumped the Codex and Cursor manifests to 1.3.0 as well so all three stay in sync (the check also enforces that). - expo-ui consolidation: main merged expo-ui-swift-ui + expo-ui-jetpack-compose into one expo-ui skill, deleting both old dirs. Accepted the deletions. Rename detection carried this branch's telemetry footer into expo-ui/references/jetpack-compose.md; took main's clean reference (footers belong in SKILL.md, not references) and instead added the Expo Skill Feedback footer to the new expo-ui/SKILL.md so the consolidated skill is still tracked. Also added a short anonymous-telemetry + opt-out note to plugins/expo/README.md for transparency now that the plugin ships usage tracking. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…, UX) Responds to review feedback from @davidmokos and @Kudo on #71. Auto-path latency (@Kudo): the PostToolUse hook no longer blocks the turn on the network. New scripts/skill-event.sh detaches the send into its own session (setsid -> perl POSIX::setsid -> nohup) and returns in ~20ms. Because a backgrounded process's stdin is /dev/null (POSIX), the wrapper reads the hook payload synchronously into an mktemp file and passes it via --hook-input-file; skill-event.js reads then unlinks it (before the telemetry-off guards, so it's cleaned up even when nothing is sent). If mktemp is unavailable it bails rather than write the payload to a predictable, world-readable /tmp path. Footer UX (@davidmokos): new scripts/skill-feedback.sh wrapper bundles run.sh + the script path, so all 16 SKILL.md footers shrink to one line. Codex (@davidmokos): corrected the stale "will light up when Codex catches up" narrative. Verified on codex-cli 0.138: plugin_hooks is a *removed* feature, and Codex runs skills by reading SKILL.md directly (no Skill tool to hook), so a plugin can't ship skill-usage hooks today. Manual skill_feedback is the Codex signal. Detail in references/telemetry.md. Honesty / consent (@Kudo): removed the false "fully inert until a key is configured" claims (a real public phc_ key ships, so telemetry is ON by default) from the scripts and AGENTS.md; the guard now only inerts a key-stripped fork. The one-time notice now fires only on visible (non-quiet) runs, so the silenced detached hook can't burn its marker unseen. Docs (@davidmokos, @Kudo): fixed the misleading "harness is auto-detected" wording (only claude-code + codex are detected); split automatic skill_invoked vs explicit skill_feedback and documented that opt-out silences both; relocated maintainer-facing config + event-shape detail to references/telemetry.md to keep SKILL.md lean; documented the prompt/chat-history non-capture stance and the Windows sh limitation. Hardening: skill name must be a single kebab segment (blocks path traversal from a malformed payload reaching path.join or the event property). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Follow-up to the skill-writing-framework review. - description: add opt-out / disable / turn-off / telemetry triggers. The skill is the only place an agent in a fresh user project learns the opt-out command, but the description only carried feedback triggers — so "turn off Expo skills telemetry" wouldn't reliably invoke it. (P0: one trigger per branch.) - SKILL.md "Harness support": collapse the medium tier to a 1-line glance + pointer; the per-harness detail (volatile, pinned to codex 0.138) now lives only in references/telemetry.md, so a Codex change is a one-place edit. (P1.) - AGENTS.md: correct the "just copy the footer" note — you must swap --skill <name>, and skill-feedback's own footer uses a different relative path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… non-existent var
The skill footers and skill-feedback SKILL.md referenced the bundled scripts as
${CLAUDE_SKILL_DIR}/... — but that variable does not exist. The Claude Code
plugins reference defines exactly three path variables substituted inline in
skill content: ${CLAUDE_PLUGIN_ROOT}, ${CLAUDE_PLUGIN_DATA}, ${CLAUDE_PROJECT_DIR}.
${CLAUDE_SKILL_DIR} was used by no other plugin and is undocumented, so it
expanded to empty and the explicit-feedback command was broken on Claude Code
(`sh "/../skill-feedback/scripts/skill-feedback.sh"`). The skill_invoked hook
path was unaffected — it already (correctly) used ${CLAUDE_PLUGIN_ROOT}.
Rewrite every reference to the official ${CLAUDE_PLUGIN_ROOT}/skills/skill-feedback/
scripts/... form (substituted inline in skill content per the docs). This also
makes all 16 footers byte-identical (no more special path for skill-feedback's
own footer), so the AGENTS.md "just copy the footer" note is now literally true.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t messaging Both events stay — automatic skill_invoked (plugin hooks, verified firing on both the AI Skill-tool and user /slash paths) and explicit skill_feedback (the agent runs the bundled script, which POSTs to PostHog). Automatic tracking only works on Claude Code, so the docs now say so plainly instead of advertising a half-working "other agents" story: - 15 product footers + skill-feedback's own: trimmed to a single Claude Code line (dropped the "Other agents (Codex, Cursor)" bullet and the wrapper-vs-js duplication). ~8-line footer -> ~5. - skill-feedback/SKILL.md: framed as Claude Code; removed the "other agents" submit/opt-out blocks; collapsed "Harness support" into a one-line pointer. - README + plugin README: telemetry sections say Claude Code only; left all the plugin-DISTRIBUTION mentions (Codex install, Run button, skills CLI) intact — those are unrelated to telemetry. - references/telemetry.md: condensed the harness deep-dive to a short "why Claude Code only" note (now citing the openai/codex source: plugin_hooks is a removed feature + no skill event in HookEventName). Kept the generic detectHarness()/--agent-harness plumbing (5 lines, harmless, labels any direct manual send correctly). No Codex hooks file is shipped. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reframes the per-skill footer from a copy-pasted command into a trigger that (a) makes the agent aware it can give feedback, (b) actively nudges it to be proactive — especially when a skill was wrong/confusing/outdated/missing — and (c) points to the `skill-feedback` skill for the actual how. The command now lives in one place (skill-feedback's "Submitting feedback"), so all 15 product footers are byte-identical with nothing per-skill to get wrong. - 15 product footers: nudge + "use the `skill-feedback` skill" pointer (no inline command). - skill-feedback/SKILL.md: "Submitting feedback" gains a proactive lead-in and a note to pass *the skill being rated* to `--skill` (the agent arrives here from another skill's footer, not to rate skill-feedback itself). Its own footer matches the nudge tone and points to that section. Tradeoff: the agent loads `skill-feedback` to get the command (one extra hop) instead of running it inline — accepted for the stronger nudge + single source. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…simplify, proactive footer - Rename the skill `skill-feedback` → `expo-skill-feedback` (dir, frontmatter name, hooks.json path, all 15 footer pointers, README/AGENTS refs). Script filenames (skill-feedback.js/.sh) and the userAgent label stay. Clearer leading word that matches the "Expo Skill Feedback" footer heading. - Feedback can now target **Expo itself**, not just the skill: skill-feedback.js gains `--about skill|expo` (default skill) -> `properties.about`, so the team can separate framework issues from skill-guidance issues. Surfaced in the footer nudge, the skill's Submitting-feedback section, and the description. - Simplify references/telemetry.md (95 -> 50 lines): keep the event shape (the privacy receipt) + add `about`, condense the rest, and point to the scripts' own comments for the detach/Windows how (single source of truth). - Bump plugin manifests 1.3.0 -> 1.4.0 (main reached 1.3.0 via #84). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Resolved the plugin.json version conflict to 1.4.0 (main reached 1.3.0 via #84; this branch needs strictly-greater). - Gave the new expo-examples skill the same proactive Expo Skill Feedback footer (pointing to expo-skill-feedback) so every skill is consistent. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove accreted complexity that wasn't earning its keep (no behavior change to what's actually tracked): - Drop client-side dedup: `$insert_id` + the `stableStringify` helper. PostHog doesn't need it (we never retry; events are already distinct). - Drop `schema_version` (premature) and `session_id_hash` (nice-to-have, not in the dashboard). - Drop the first-run stderr notice (`maybeShowFirstRunNotice` + `NOTICE_PATH`) — it was near-dead (hidden hook stderr); disclosure lives in the README + skill. - Trim telemetry_common exports to what's actually imported. - references/telemetry.md: drop the "Implementation notes" section (it duplicated the scripts' own comments — single source of truth) and the session-hash line. telemetry_common.js 224->148, references/telemetry.md 95->41. Event payloads now carry only: skill, agent_harness, initiator/about, os/arch, installation_id_hash (+ rating/feedback_text for skill_feedback). Verified both dry-runs + the toggle. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The scripts/package.json existed only to pin "type":"commonjs" so require() works regardless of any parent "type":"module". Renaming the four scripts to .cjs declares CommonJS via the extension itself — Node and Bun both honor it — so the package.json is no longer needed and is removed. One fewer file, and the module type is now self-evident instead of hidden in a config file. - skill-event/skill-feedback/telemetry/telemetry_common .js -> .cjs - updated require()s, the run.sh/skill-event.sh/skill-feedback.sh references, and the SKILL.md / AGENTS.md / references mentions - verified under both node and bun, and through the run.sh wrapper chain Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Apply the writing-great-skills lens to the skill body — the agent that loads this skill only needs to know how to submit feedback and how to turn telemetry off. - Drop "## Runtime" (node/bun/run.sh internals — a no-op for the agent; it just runs the command). - Drop the "## The two signals" table (duplicated the intro; the full mechanism lives in references/telemetry.md). - Drop "## Notes for maintainers" (maintainer info in the agent's context) — keep only a one-line pointer to references/telemetry.md. - Tighten the intro, submit, and opt-out sections. SKILL.md ~90 -> 52 lines. No behavior change; the two functions + the privacy one-liner + the references pointer remain. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
~90% of references/telemetry.md restated the code: the event shape is built in skill-event.cjs / skill-feedback.cjs (and summarized in SKILL.md's privacy line), and the PostHog-key override is already a comment in telemetry_common.cjs. The only unique content — why Claude Code only — was one sentence; folded it into the AGENTS.md telemetry note (which previously just pointed here). Removed the SKILL.md and AGENTS.md pointers and deleted the file + the now-empty references/ dir. expo-skill-feedback is now just SKILL.md + scripts/. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The skill_invoked hook and the feedback/opt-out commands shelled out via
`sh`, which isn't present on stock Windows. Switch every entry point to call
`node` directly — portable across cmd/PowerShell/sh, and the convention top
skills.sh skills already use ("name the interpreter, relative path").
Fold the non-blocking detach into skill-event.cjs itself: the foreground
process reads the hook payload from stdin, resolves the skill, runs the cheap
local gates, then re-execs a detached copy (spawn detached + unref + windowsHide)
to do the PostHog POST off the agent's critical path. Because Node reads stdin
before detaching, the old temp-file / setsid / perl / nohup handoff is gone.
- hooks/hooks.json: sh skill-event.sh -> node skill-event.cjs --detach
- SKILL.md: sh skill-feedback.sh / run.sh -> node skill-feedback.cjs / telemetry.cjs
- AGENTS.md: prefix the opt-out command with node
- delete run.sh, skill-event.sh, skill-feedback.sh (-67 lines of shell)
Same anonymous, opt-out, Claude-Code-only behavior; now cross-platform.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
davidmokos
left a comment
There was a problem hiding this comment.
Lookin at the telemetry code I'm wondering whether to have expo feedback command in the CLI directly for that.
|
|
||
| --- | ||
|
|
||
| ## Expo Skill Feedback |
There was a problem hiding this comment.
I don't like that we're repeating the same paragraph for each skill here. Maybe it can be added via a CI script?
Kudo
left a comment
There was a problem hiding this comment.
thought i'm still not sure whether adding telemetry in the plugin would be a good idea. i don't want to block it. we could try that and see how it goes
Nothing is sent — no skill_invoked, no feedback — until the user enables telemetry. The hook stays installed but no-ops behind the new telemetryActive() gate. Precedence: explicit-off (DO_NOT_TRACK / EXPO_SKILLS_TELEMETRY=0) → CI (never sends) → explicit-on (=1) → opt-in marker (~/.expo-skills/opt-in) → default off. - telemetry.cjs: --on writes the opt-in marker, --off removes it (the default); --status explains the current state and why - Conversational switch: the expo-skill-feedback skill teaches the agent to run --on when the user says "enable Expo skills telemetry" (or agrees when offered), to answer status questions by running --status rather than from memory, and to never enable telemetry on its own — a refused feedback send asks the user once, then respects the answer - Remove the "Expo Skill Feedback" footer from all 16 product skills: SKILL.md bodies aren't templated, so a footer can't reflect the opt-in state; feedback now lives only in the expo-skill-feedback skill - READMEs + AGENTS.md: replace the on-by-default telemetry sections with the shorter opt-in story Verified end-to-end in real claude -p sessions with the plugin loaded: "enable expo skills telemetry" triggers the skill and writes the marker; status queries run --status; the hook fires but spawns nothing while off, and sends through the detached child once enabled. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Conflicts were the three plugin manifests' version fields (branch 1.4.0 vs main's 1.5.0 release): resolved to 1.6.0 in all three together, since this PR ships a new feature (opt-in telemetry) on top of main's release — matching what scripts/check-plugin-version-bump.ts expects. Brings in from main: eas-simulator and web-to-native skills, expo-skill-eval, the skill-limits CI check, expo-ui/building-native-ui/expo-dev-client improvements, and the EAS Workflow dedupe. Main's new skills carry no feedback footer, so no re-stripping was needed; both READMEs auto-merged keeping the opt-in telemetry sections. Verified: skill-limits check, version-bump check, the 22-test telemetry harness, and plugin validation all pass on the merged tree. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Same opt-in facts, half the words: keep the default + toggles, the two Claude-Code-only events and their wiring, the telemetryActive() pointer, and the contributor rules (no footers, no per-skill telemetry edits, Codex/Cursor can't host hooks — don't re-investigate). Drop the gate precedence chain and templating rationale; the code documents those. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Summary
Adds opt-in, anonymous usage telemetry and a feedback path to the Expo skills plugin — so we can see which skills get used, and where they fall short. Off by default: a fresh install sends nothing, creates nothing (not even a local id), until the user enables it. Claude Code only; scripts are zero-dependency Node and run the same on macOS, Linux, and Windows.
Signals
skill_invokedinitiator: ai(Skill tool) oruser(/slashcommand)hooks/hooks.json(Claude Code)skill_feedbackexpo-skill-feedbackskillConsent model
~/.expo-skills/dir, no install id.telemetry.cjs --on; orEXPO_SKILLS_TELEMETRY=1--off(default state),EXPO_SKILLS_TELEMETRY=0, orDO_NOT_TRACK=1— explicit-off always wins; CI never sends--status, and never to enable telemetry on its own — a refused feedback send asks the user onceChanges
.cjs) inplugins/expo/skills/expo-skill-feedback/scripts/; the master gate istelemetryActive()intelemetry_common.cjs.nodedirectly (works on cmd/PowerShell/sh). The hook self-detaches in Node (spawn+unref) so the POST never blocks the agent turn.hooks/hooks.json(PostToolUse[Skill]+UserPromptExpansion); no per-skill hooks.expo-skill-feedbackskill.Privacy
os/arch), and a hash of a random local install id — never PII, code, prompts, or file paths.us.i.posthog.com/i/v0/e/.Testing
claude -p --plugin-dir): "enable expo skills telemetry" triggers the skill and writes the opt-in marker; status questions run--status; the hook fires but spawns nothing while off, and sends via the detached child once enabled.claude plugin validate.🤖 Generated with Claude Code