How Superpowers Forces Skill Execution
- Superpowers injects the using-superpowers skill into context at session start, so the model knows the skill rules before its first response.
- Claude Code's default skill system can be unreliable because the model only sees a skill's name and description, then has to decide whether to call the Skill tool itself.
- The key pieces behind Superpowers' higher execution rate are the SessionStart hook, additionalContext injection, platform-specific output formats, and async: false.
How Superpowers Forces Skill Execution
This post is based on notes written about a month ago. Superpowers and each CLI's hook/skill behavior are changing quickly, so some implementation details may differ from the current versions.
TL;DR
If you use AI agents for long enough, you eventually notice that skills do not always activate as reliably as you expect. Superpowers feels different. The secret is its SessionStart hook. At the beginning of a session, it forcibly injects the full using-superpowers skill into context, so the model already knows "I need to use skills" before its first response. It looks like a simple plugin setup, but it is actually a fairly deliberate mechanism that can lift skill execution from 10% to 66%.
Why don't skills activate automatically?
Most people using AI tools today probably know what skills are. But after using them for a while, one thing becomes obvious: unless you call a command directly or explicitly name the skill, skills often do not behave the way you expect.
What we want is for the model to read the title and description frontmatter, infer the right skill with 99% confidence, and run it automatically. Reality is different. Skills such as GSD or gstack often do not work properly unless you invoke them directly.
I recommended Superpowers to a coworker and said, "Just use it naturally," when they asked whether there were any commands or skills they should know. Then I started wondering: if several skills are mixed together, the model might miss the one it needs. So why does Superpowers feel unusually reliable? I opened the code and took a look.
What I found was a custom hook and a script that effectively force the agent to use the right skill from the skillset with much higher probability. The pattern seemed useful enough for other services too, so I decided to trace how Superpowers works from user input, to hook execution, to skill discovery, to final skill execution.
How skill systems work by default

At session start, Claude Code scans skills from three locations: organization-wide (/etc/claude-code/.claude/skills/), user-level (~/.claude/skills/), and project-level (.claude/skills/).
After scanning, the model receives only each skill's name and one-line description. The full skill content enters context only when the model explicitly calls the Skill tool. In other words, the model itself has to decide, "This task needs this skill," before execution happens.
That is the root of the problem. Making the right decision from just a name and a one-line description is much less reliable than it sounds. According to the experiment data, skill execution was only 10% in multi-turn sessions and 6% in single-turn sessions.
How Superpowers bypasses the problem

Superpowers does not depend on the skill system itself. It uses a hook to skip that step entirely. The flow looks like this.
Step 1: Register the hook (hooks/hooks.json)
hooks.json registers a hook for the SessionStart event. In the actual code, the matcher covers three triggers: startup|clear|compact. It then runs the session-start script through run-hook.cmd. The hook is configured with async: false, so the model's first response does not begin until the script finishes.
{
"SessionStart": [
{
"matcher": "startup|clear|compact",
"hooks": [
{
"type": "command",
"command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cmd\" session-start",
"async": false
}
]
}
]
}Step 2: Run the script (hooks/session-start)
When the session-start script runs, it reads the entire ${PLUGIN_ROOT}/skills/using-superpowers/SKILL.md file into a variable. It then wraps that content in an <EXTREMELY_IMPORTANT> tag and outputs it as JSON. The actual output shape looks like this.
{
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": "<EXTREMELY_IMPORTANT>\nYou have superpowers.\n\n..."
}
}The current code in v5.x branches the output format by platform. Claude Code expects hookSpecificOutput.additionalContext, Cursor expects additional_context, and Copilot CLI expects top-level additionalContext, so the script checks environment variables and emits the appropriate shape.
Step 3: Inject context
Claude Code turns hookSpecificOutput.additionalContext into a <system-reminder> message and injects it into context. Before the model's first response, the full using-superpowers skill is already inside the context window.
Step 4: Start the conversation with skill awareness
The model begins the conversation already knowing what skills exist, when to use them, and why it must use them. The model no longer needs to discover those rules on its own before acting.
How using-superpowers enforces the rule

The injected content is not just a friendly guide. It is an instruction block wrapped in <EXTREMELY_IMPORTANT>, and using-superpowers/SKILL.md even contains a decision graph for the skill execution flow. Roughly, the flow is:
- Receive the user message
- If about to enter Plan Mode → check the brainstorming skill
- If there is even a 1% chance a skill applies → load the full content with the
Skilltool - Follow the skill, and if it has a checklist, create TodoWrite items
Then it explicitly declares "The Rule": invoke relevant skills before any response or action. It even lists the kinds of internal rationalizations a model might use to skip a skill and blocks each of them as a red flag.
- "This is just a simple question" → Rationalization
- "The skill is overkill" → Rationalization
- "I need more context first" → Rationalization
Platform-specific invocation
Superpowers supports Claude Code, Cursor, Codex, OpenCode, Copilot CLI, and Gemini CLI. But each platform has a different hook system, so the way session-start is invoked differs by platform.
Claude Code / Cursor / Copilot CLI: these use hook-based context injection. Each platform's hooks.json or hooks-cursor.json registers the SessionStart event, and the session-start script detects environment variables such as CURSOR_PLUGIN_ROOT, CLAUDE_PLUGIN_ROOT, or COPILOT_CLI to output the platform-specific JSON format. Claude Code uses hookSpecificOutput.additionalContext, Cursor uses additional_context, and Copilot CLI uses the SDK-standard additionalContext.
Codex: Codex does not have a hook system. Instead, it uses native skill discovery. Installation is just a symlink.
gh repo clone obra/superpowers ~/.codex/superpowers
mkdir -p ~/.agents/skills
ln -s ~/.codex/superpowers/skills ~/.agents/skills/superpowersCodex automatically scans the ~/.agents/skills/ directory at startup and loads SKILL.md files based on frontmatter metadata. There is no plugin.json or hooks.json. Instead, the description field of the using-superpowers meta skill acts as Codex's auto-activation trigger.
Gemini CLI: Gemini uses the activate_skill tool. It loads skill metadata at session start, then activates the full content on demand when the model calls activate_skill.
The platform differences can be summarized like this.
| Platform | Mechanism | Trigger |
|---|---|---|
| Claude Code | Hook + additionalContext injection | SessionStart event |
| Cursor | Hook + additional_context injection | SessionStart event |
| Copilot CLI | Hook + SDK-standard injection | SessionStart event |
| Codex | Symlink + native discovery | Directory scan |
| Gemini CLI | activate_skill tool | Metadata-based activation |
They all use the same skill library, but the entry point is implemented differently on each platform. So if Codex or Gemini feels good at activating skills, that may be because the platform's own skill discovery is more aggressive, not because of the Superpowers hook.
The history behind the enforcement mechanism
The release notes and commit history show that the current structure did not appear fully formed.
Early version: the hook only passed the path to getting-started/SKILL.md and asked the model to read it. The injected session-start content looked roughly like this.
<EXTREMELY_IMPORTANT>
You have Superpowers. RIGHT NOW, go read:
@/path/to/skills/getting-started/SKILL.md
</EXTREMELY_IMPORTANT>This approach told the model to read the file. But sometimes the model simply did not.
Middle stage: when getting-started was renamed to using-superpowers, the approach changed. Instead of passing a file path, the script read the full SKILL.md itself and injected the entire content through additionalContext. That removed the step where the model had to decide whether to read it.
Continued tightening: cases where the model skipped skills still appeared, so each version tightened the instructions further.
- Added the
<EXTREMELY_IMPORTANT>block, stronger absolute language, and a Red Flags table that pre-lists rationalization patterns - Changed "Check for skills" to "Invoke relevant or requested skills" because models sometimes skipped a skill when the user explicitly named it, reasoning that they already knew it
- Changed "before responding" to "BEFORE any response or action" because models sometimes acted first without replying
- Changed
async: truetoasync: falseafter a race condition was found where the first response could start before the hook finished
This is not a controlled numerical proof. But the patch history itself is honest evidence. The project evolved from "please read this" to "you must invoke this," showing version by version how hard it is to make a model choose skills reliably on its own.
Remaining limitations
The approach is not perfect. SessionStart hooks may not fire for subagent sessions, so subagents can run without the injected context and behave like ordinary models. GitHub issue #237 discusses adding a SubagentStart hook. Also, after context compaction, injected content can be dropped, so long sessions may need the rules to be reloaded.
Hook execution can also be unstable on Windows. The project has changed its approach across versions, from running .sh files directly to using the run-hook.cmd wrapper, and related issues are still open.
Closing thoughts
The core idea behind Superpowers is simpler than it looks: instead of making the model discover skills by itself, push the rules into context the moment a session starts. The enforcement may feel aggressive, but the execution-rate data suggests that it works.
This is a pattern other skill-based workflows can reuse. Put repeatedly applied best practices in CLAUDE.md, keep situation-specific procedures in skills, and if skill execution is still unreliable, inject the key instructions through a SessionStart hook to get a similar effect with relatively little machinery.
Refs
- Skills for Claude! – blog.fsck.com
- Superpowers for Claude Code – blog.fsck.com
- obra/superpowers – GitHub
- hooks/session-start – GitHub
- skills/using-superpowers/SKILL.md – GitHub
- Stop Putting Best Practices in Skills – Codeminer42
- Extend Claude with skills – Claude Code Docs
- Subagents missing hook-injected context – GitHub Issue #237
- obra/superpowers RELEASE-NOTES.md