-
Notifications
You must be signed in to change notification settings - Fork 1
Bug: /design skill fails to load on zsh due to shell glob expansion errors #544
Description
Bug: /design skill fails to load on zsh due to shell glob expansion errors
User context
First-time crosslink user following the design workflow guide at
https://forecast.bio/crosslink/guides/design-workflow.html.
Following the guide, I attempted to start a design session by running this in the terminal:
crosslink design "write two claude skills. the first is a technical writer..."
This produced the error unknown option '--prompt' with no indication that design is not
a crosslink CLI subcommand or that the correct invocation is /design inside Claude Code.
I eventually figured out the correct approach — open Claude Code, then type /design "<description>" — but only through trial and error, not from the error message or the guide.
This report covers both the CLI confusion (documented below as Bug 0) and the two shell
compatibility bugs that caused /design to fail once I found the correct invocation.
Summary
The /design skill fails to load entirely on zsh when the .design/ directory does not
exist, or when any of the listed architecture files are absent. Both failures manifest as
Error: Shell command failed for pattern "!..." before the skill prompt is even processed,
meaning the user's feature description is never acted on.
Environment
- Shell: zsh (macOS default)
- Skill file:
.claude/commands/design.md
Bug 0 — crosslink design gives a misleading error instead of helpful guidance
Error
error: unknown option '--prompt'
What happened
Running crosslink design "<description>" in a terminal produces this error. The error
message references --prompt, which is unrelated to what the user typed and gives no hint
that:
designis not a crosslink CLI subcommand- The correct invocation is
/designinside a Claude Code session - The guide at https://forecast.bio/crosslink/guides/design-workflow.html does not make
this distinction explicit enough for first-time users
Expected behavior
The crosslink CLI should either:
- Detect that
designis a known Claude Code skill and print guidance:
'design' is a Claude Code skill. Open Claude Code and type: /design "<description>" - Or at minimum:
error: unknown command 'design'. Run 'crosslink help' for available commands.
The current error (unknown option '--prompt') is actively misleading — it implies the user
passed a bad flag rather than an unrecognized command.
Bug 1 — Glob expansion failure when .design/ does not exist
Error
Error: Shell command failed for pattern "!`ls .design/*.md 2>/dev/null`": [stderr]
(eval):1: no matches found: .design/*.md
Root cause
Line 11 of design.md runs:
ls .design/*.md 2>/dev/nullIn zsh, glob patterns are expanded by the shell before the command runs. When .design/
does not exist, zsh throws no matches found: .design/*.md at the expansion stage — before
ls is invoked — so 2>/dev/null never suppresses it. The exit is non-zero and the harness
treats the skill as failed. In bash this would silently pass the literal glob to ls, which
would then fail quietly.
Fix applied
- - Existing design docs: !`ls .design/*.md 2>/dev/null`
+ - Existing design docs: !`find .design -name '*.md' 2>/dev/null`find does not use shell glob expansion, so it returns empty output (exit 0) when
.design/ is absent.
Bug 2 — ls exits non-zero when some listed files are absent
Error
Error: Shell command failed for pattern "!`ls README.md CLAUDE.md ARCHITECTURE.md ADR.md 2>/dev/null`": [stderr]
README.md
Root cause
Line 12 of design.md runs:
ls README.md CLAUDE.md ARCHITECTURE.md ADR.md 2>/dev/nullWhen ls is given a list of explicit filenames and any of them do not exist, it exits with
a non-zero code even though it successfully lists the ones that do exist. 2>/dev/null
suppresses the "No such file" messages but does not change the exit code. In a typical
project that has README.md but not CLAUDE.md, ARCHITECTURE.md, or ADR.md, this
command always exits non-zero and the harness rejects the skill context.
Fix applied
- - Architecture files: !`ls README.md CLAUDE.md ARCHITECTURE.md ADR.md 2>/dev/null`
+ - Architecture files: !`find . -maxdepth 1 \( -name 'README.md' -o -name 'CLAUDE.md' -o -name 'ARCHITECTURE.md' -o -name 'ADR.md' \) 2>/dev/null`find with explicit -name predicates always exits 0 and lists only the files that exist,
regardless of how many are absent.
Bug 3 — /check skill fails to load when Docker is not installed
Context
After successfully launching a background agent with crosslink kickoff run .design/documentation-skills.md,
the agent prompted for tool-use approval inside its tmux session. I ran /check WikX-0BtG-design-documentation-skills-md-b1be
to inspect the agent's status without attaching to tmux directly. The /check skill failed
to load entirely, making it impossible to check on or interact with the running agent. The
only recourse was to attach to the tmux session manually — defeating the purpose of /check.
Error
Error: Shell command failed for pattern "!`docker ps --filter label=crosslink-agent=true --format 'table {{.Names}}\t{{.Status}}' 2>/dev/null`":
Skill file
.claude/commands/check.md
Root cause
Line 8 of check.md runs:
docker ps --filter label=crosslink-agent=true --format 'table {{.Names}}\t{{.Status}}' 2>/dev/nullWhen Docker is not installed, docker is not found and the shell exits non-zero before
2>/dev/null can suppress anything. The harness treats any non-zero exit as a fatal
context-gathering failure and refuses to load the skill — even though docker is entirely
optional (the skill supports both docker containers and tmux sessions).
Fix applied
- - Running containers: !`docker ps --filter label=crosslink-agent=true --format 'table {{.Names}}\t{{.Status}}' 2>/dev/null`
+ - Running containers: !`docker ps --filter label=crosslink-agent=true --format 'table {{.Names}}\t{{.Status}}' 2>/dev/null || true`Appending || true forces exit 0 regardless of whether docker is installed or running,
so the skill loads normally on machines that use tmux only.
Recommendation
All !...`` shell commands in skill context sections that use glob patterns or ls with
multiple filenames should be audited for this class of failure. The safe patterns are:
| Instead of | Use |
|---|---|
ls dir/*.ext 2>/dev/null |
find dir -name '*.ext' 2>/dev/null |
ls file1 file2 file3 2>/dev/null |
find . -maxdepth 1 \( -name 'file1' -o -name 'file2' -o -name 'file3' \) 2>/dev/null |
ls dir/ 2>/dev/null |
find dir -maxdepth 0 2>/dev/null (or [ -d dir ] && ls dir/) |
optional-tool subcommand 2>/dev/null |
optional-tool subcommand 2>/dev/null || true |
These alternatives are portable across bash and zsh and always exit 0 when no matches
are found or when optional tooling is absent.