| 1 | --- |
| 2 | title: Skill NUX belongs in SKILL.md Step 0, not a SessionStart hook |
| 3 | date: 2026-09-17 |
| 4 | category: docs/solutions/workflow-issues |
| 5 | module: skills/last30days/SKILL.md |
| 6 | problem_type: workflow_issue |
| 7 | component: development_workflow |
| 8 | severity: medium |
| 9 | symptoms: |
| 10 | - Claude Code or Grok sessions print last30days status even when /last30days is not invoked |
| 11 | - ~/Documents/Last30Days is created on every session start |
| 12 | - Unconfigured installs see a setup-wizard pitch in every project, indefinitely |
| 13 | root_cause: design_error |
| 14 | resolution_type: code_fix |
| 15 | related_components: |
| 16 | - hooks/hooks.json |
| 17 | - hooks/scripts/check-config.sh |
| 18 | - last30days.py --preflight |
| 19 | tags: |
| 20 | - session-start |
| 21 | - nux |
| 22 | - preflight |
| 23 | - plugin-hooks |
| 24 | - onboarding |
| 25 | --- |
| 26 | |
| 27 | # Skill NUX belongs in SKILL.md Step 0, not a SessionStart hook |
| 28 | |
| 29 | ## Problem |
| 30 | |
| 31 | The plugin installed a `SessionStart` hook (`hooks/scripts/check-config.sh`) that ran in every Claude Code and Grok session, in every project, whether or not anyone invoked `/last30days`. It printed a welcome or source-count banner, optionally upsold ScrapeCreators, created `~/Documents/Last30Days`, scanned Keychain item names, and auto-chmod'd `.env` files. |
| 32 | |
| 33 | That is the wrong layer for skill onboarding. A trusted plugin hook cannot be "just for last30days users." |
| 34 | |
| 35 | ## Root cause |
| 36 | |
| 37 | [PR #58](https://github.com/mvanhorn/last30days-skill/pull/58) (2026-03-10) added a silent-when-configured missing-keys warning. [PR #126](https://github.com/mvanhorn/last30days-skill/pull/126) (v2.9.6) rewrote it into an always-print NUX. Later PRs piled on mkdir (#476), Keychain presence, and security patches (#914 cwd-adjacent `.env` RCE, #1074 decoy `check-config.sh` from session cwd). `--preflight` in the non-modal first-run flow was a second copy of the same permission summary. |
| 38 | |
| 39 | SKILL.md Step 0 already owns consent (cookies, ScrapeCreators). The engine already creates the save dir on first save. `last30days.py --preflight` is a secret-free inspector for humans, scripts, and the MCP `preflight` tool — not a required first-run beat. |
| 40 | |
| 41 | ## Solution |
| 42 | |
| 43 | - Do not ship `hooks/hooks.json` or a SessionStart command. Plugin auto-discovery would load it. |
| 44 | - First-run NUX stays in SKILL.md Step 0 (modal / non-modal / Grok Bot). Detect setup from key presence; do not dump `.env`. |
| 45 | - `--preflight` stays as an opt-in inspector and frozen JSON contract. Do not run it as a required first-run step. |
| 46 | - `lib/preflight.py` (Class 1 query refuse-gate) is unrelated and stays in the engine. |
| 47 | |
| 48 | ## Prevention |
| 49 | |
| 50 | If a future change wants "status at session start," put it behind an explicit user-installed hook or a slash-command invocation. A marketplace plugin SessionStart hook is not an opt-in. |
| 51 |