From 6c2c55733c27c9101fcb41a91aa946c45ba6344f Mon Sep 17 00:00:00 2001 From: Trevin Chow Date: Fri, 15 May 2026 22:14:37 -0700 Subject: [PATCH] fix(skill): use find instead of ls+glob in cache resolvers (zsh compatibility) zsh errors on globs that match nothing instead of returning the literal pattern (bash's default), and `2>/dev/null` does not suppress the error because it comes from the shell's glob expansion before `ls` even runs. Under Codex (which executes the SKILL.md bash via zsh), STEP 0 and the Step 1 / comparison-mode resolvers emitted noisy "no matches found" errors on machines without a Claude plugin cache populated. Replaces all three `ls -d $HOME/.claude/plugins/cache/last30days-skill/last30days/*/` invocations with `find ... -mindepth 1 -maxdepth 1 -type d 2>/dev/null`. find is POSIX-portable, errors silently when the base dir doesn't exist, and never triggers shell glob errors. `sort -V | tail -1` precedence preserved (verified: picks 3.10.0 over 3.2.1 over 3.1.0). Trailing-slash strip removed because find doesn't append slashes. Observed in Codex session running /last30days against PR #400 with the Claude plugin cache deleted - bash output was: zsh:1: no matches found: /Users/.../last30days/*/ After fix: clean empty output, exit 0, STEP 0 correctly treats it as "no cache present, do not hop", resolver falls through to per-harness skill dirs as designed. --- skills/last30days/SKILL.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/skills/last30days/SKILL.md b/skills/last30days/SKILL.md index 1422be6..be86600 100644 --- a/skills/last30days/SKILL.md +++ b/skills/last30days/SKILL.md @@ -67,7 +67,7 @@ Before reading anything else in this file, check whether you loaded SKILL.md fro **Run this check:** ```bash -CLAUDE_CACHE_LATEST=$(ls -d "$HOME/.claude/plugins/cache/last30days-skill/last30days"/*/ 2>/dev/null | sort -V | tail -1) +CLAUDE_CACHE_LATEST=$(find "$HOME/.claude/plugins/cache/last30days-skill/last30days" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | sort -V | tail -1) CLAUDE_CACHE_LATEST="${CLAUDE_CACHE_LATEST%/}" echo "CLAUDE_CACHE_LATEST=$CLAUDE_CACHE_LATEST" ``` @@ -586,8 +586,7 @@ When the user asks "X vs Y" (or "X vs Y vs Z"), the engine fans out N full `pipe # Comparison mode skips Step 1, so resolve SKILL_ROOT inline here (same precedence # walk as Step 1 — keep the two in sync if you edit either). SKILL_ROOT="" -CLAUDE_PLUGIN_ROOT="$(ls -d "$HOME/.claude/plugins/cache/last30days-skill/last30days/"*/ 2>/dev/null | sort -V | tail -1)" -CLAUDE_PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT%/}" +CLAUDE_PLUGIN_ROOT="$(find "$HOME/.claude/plugins/cache/last30days-skill/last30days" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | sort -V | tail -1)" if [ -n "$CLAUDE_PLUGIN_ROOT" ]; then if [ -f "$CLAUDE_PLUGIN_ROOT/skills/last30days/scripts/last30days.py" ]; then SKILL_ROOT="$CLAUDE_PLUGIN_ROOT/skills/last30days" @@ -900,8 +899,9 @@ SKILL_ROOT="" # 1. Claude Code plugin cache (versioned, sort -V picks freshest). Two cache layouts ship in the wild: # nested ({cache}/{version}/skills/last30days/scripts/...) and flat ({cache}/{version}/scripts/...). -CLAUDE_PLUGIN_ROOT="$(ls -d "$HOME/.claude/plugins/cache/last30days-skill/last30days/"*/ 2>/dev/null | sort -V | tail -1)" -CLAUDE_PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT%/}" +# `find` (not `ls + glob`) because zsh errors on globs that match nothing, leaking +# noisy "no matches found" stderr in Codex/zsh sessions even with 2>/dev/null. +CLAUDE_PLUGIN_ROOT="$(find "$HOME/.claude/plugins/cache/last30days-skill/last30days" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | sort -V | tail -1)" if [ -n "$CLAUDE_PLUGIN_ROOT" ]; then if [ -f "$CLAUDE_PLUGIN_ROOT/skills/last30days/scripts/last30days.py" ]; then SKILL_ROOT="$CLAUDE_PLUGIN_ROOT/skills/last30days"