I picked up my phone to check on a job I'd started at my desk. I typed /pickup. Unknown command.
That shouldn't happen. /pickup is a skill I lean on constantly — session handover, priorities, picking up exactly where I left off. It works every time on desktop. On mobile, nothing.
The missing submodule
The skill lives in shulkerbox, a repo I keep separate from monospace.studio and pull in as a git submodule. The name is a Minecraft reference — a shulker box is a storage block that keeps its contents intact no matter where you carry it.
Mine hadn't travelled anywhere. It had never even been unpacked.
On this environment the directory existed in .gitmodules but the working copy was empty — a pointer to nothing. Worse: even after I initialized it, the skills still didn't show up.
Claude Code only discovers skills from .claude/skills/, and shulkerbox's actual skills live at shulkerbox/skills/core/pickup — a different path entirely. Two separate failures stacked on top of each other, and either one alone would have been enough to break it.
Bridging it
The fix was a symlink for every skill: .claude/skills/pickup → ../../shulkerbox/skills/core/pickup, and eight more like it for wrap-up, handover, sovereyn, and the rest. Nine lines, tracked in git — which meant carving an exception into a .gitignore rule that blanket-excluded .claude/ as an "ai tools" directory.
Then the harder question: how does the submodule get cloned in the first place, on a machine that's never seen this repo before? A SessionStart hook, gated to remote sessions only, running git submodule update --init the moment a session opens.
Recursion has a cost
My first pass used --recursive. It worked, and it also cloned a second, redundant copy of shulkerbox — because council, another submodule in this repo, vendors its own nested copy.
Fine today. But --recursive means the depth of what gets cloned is decided by repos I don't control, not by me.
I dropped it to one level. council/shulkerbox stays uninitialized unless someone explicitly asks for it, and I wrote the reasoning into AGENTS.md so the next agent — human or otherwise — doesn't reintroduce it without knowing why.
The actual point
None of this is exciting. It's a symlink, a gitignore exception, and a hook script. But it's the difference between an agent workflow that only exists on one laptop and one that survives switching devices mid-task — which is the whole premise of working this way in the first place.
Every post on this site about agents building things autonomously assumes the plumbing underneath just works. Usually I don't see it fail. This time I did, from my phone, mid-session — and fixing it took longer than everything else in that session put together.