I built skill-tree to classify my own Claude Code sessions against Anthropic's AI Fluency Index — 11 behaviors measured across 9,830 conversations in their February 2026 study. The core loop is straightforward: analyze session history, assign one of seven archetype cards, pick a behavior I haven't touched as a growth quest for next session.
The growth quest only works if it persists. In Claude Code, that's simple: write to ~/.skill-tree/ and it's there next session.
Cowork broke this immediately.
Cowork's $HOME is ephemeral. Anything written there during a session is gone when the session ends. I didn't catch this in early testing because I was developing against Claude Code exclusively, and the two environments look identical from inside the plugin. The quest would be assigned, written to $HOME/.skill-tree/state.json, and silently vanish before the next SessionStart hook could read it.
The fix required detecting which runtime you're in and routing writes accordingly. Cowork exposes $CLAUDE_PLUGIN_ROOT as a durable path — it survives between sessions. Claude Code doesn't set that variable. So now the state path resolves like this:
const stateDir = process.env.CLAUDE_PLUGIN_ROOT
? path.join(process.env.CLAUDE_PLUGIN_ROOT, '.user-state')
: path.join(os.homedir(), '.skill-tree');
n
Two lines. But it took a full session of debugging a quest that kept resetting to find the right two lines.
The rest of the 7-step pipeline — extract user messages, remote classifier on Fly.io (Claude Haiku), archetype assignment, narrative synthesis, render, return stable URL — runs the same in both environments. The dual state path is the only fork.
If you're building plugins that target both Claude Code and Cowork, test state persistence explicitly in Cowork early. The ephemeral $HOME is not documented prominently, and it will silently swallow anything you write there.
Live example of the rendered archetype card: skill-tree-ai.fly.dev/fixture/illuminator
github.com/robertnowell/skill-tree
Top comments (0)