Skip to content

Commit 5fa487b

Browse files
authored
chore: fix claude and codex symlinking tests (#23599)
Fixes issue introduced in #23593. Also fixes the content hash so they run on any change to claude or codex folders, which caused the test failure to go unnoticed in the PR where it was introduced.
1 parent 129eb13 commit 5fa487b

3 files changed

Lines changed: 36 additions & 52 deletions

File tree

.claude/bootstrap.sh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
# `test`. Keeps hook scripts and their tests as a self-contained component.
55
source $(git rev-parse --show-toplevel)/ci3/source_bootstrap
66

7-
hash=$(cache_content_hash ^.claude)
7+
# Hash everything agents_symlink_test inspects, not just .claude/: the .codex mirrors and the root
8+
# AGENTS.md/CLAUDE.md symlinks live outside .claude/, so a change there must still rerun the test.
9+
hash=$(cache_content_hash "^.*.claude" "^.*.codex" "^AGENTS.md" "^CLAUDE.md")
810

911
function test_cmds {
1012
# source_base cd's us into .claude/, so glob relative-to-here, but emit paths

.claude/tests/agents_symlink_test

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -82,17 +82,43 @@ while IFS= read -r dir; do
8282
"$ROOT"/noir/noir-repo/*) continue;;
8383
esac
8484
codex="$(dirname "$dir")/.codex"
85-
if [[ ! -L "$codex" ]]; then
86-
fail "${codex#$ROOT/} is missing or is not a symlink to .claude"
85+
rel=${codex#$ROOT/}
86+
# A .codex may either be a symlink to its sibling .claude (the repo root keeps this form), or a
87+
# real directory whose immediate children symlink into .claude (the per-package form, required
88+
# because a sandbox cannot bind-mount a path that is itself a symlink). Either way Codex must see
89+
# exactly the same contents as .claude.
90+
if [[ -L "$codex" ]]; then
91+
resolved=$(cd "$(dirname "$codex")" && cd "$(readlink "$codex")" 2>/dev/null && pwd -P) || resolved=""
92+
claude_resolved=$(cd "$dir" && pwd -P)
93+
if [[ "$resolved" != "$claude_resolved" ]]; then
94+
fail "$rel is a symlink to ${resolved:-?}, expected its sibling .claude ($claude_resolved)"
95+
else
96+
pass "$rel -> .claude"
97+
fi
8798
continue
8899
fi
89-
resolved=$(cd "$(dirname "$codex")" && cd "$(readlink "$codex")" 2>/dev/null && pwd -P) || resolved=""
90-
claude_resolved=$(cd "$dir" && pwd -P)
91-
if [[ "$resolved" != "$claude_resolved" ]]; then
92-
fail "${codex#$ROOT/} resolves to $resolved, expected $claude_resolved"
100+
if [[ ! -d "$codex" ]]; then
101+
fail "$rel is missing (expected a directory mirroring .claude via child symlinks)"
93102
continue
94103
fi
95-
pass "${codex#$ROOT/}"
104+
codex_ok=1
105+
# Forward: every entry in .claude must have a matching symlink in .codex.
106+
while IFS= read -r child; do
107+
name=$(basename "$child")
108+
if [[ ! -L "$codex/$name" || ! "$codex/$name" -ef "$child" ]]; then
109+
fail "$rel/$name should be a symlink to ../.claude/$name"
110+
codex_ok=0
111+
fi
112+
done < <(find "$dir" -mindepth 1 -maxdepth 1)
113+
# Reverse: every entry in .codex must correspond to a .claude entry (no stale or dangling links).
114+
while IFS= read -r entry; do
115+
name=$(basename "$entry")
116+
if [[ ! -e "$dir/$name" && ! -L "$dir/$name" ]]; then
117+
fail "$rel/$name is stale; no matching .claude/$name"
118+
codex_ok=0
119+
fi
120+
done < <(find "$codex" -mindepth 1 -maxdepth 1)
121+
(( codex_ok )) && pass "$rel"
96122
done < <(find "$ROOT" -type d -name .claude -not -path "$ROOT/noir/*" -not -path "$ROOT/**/node_modules/*")
97123

98124
echo

yarn-project/precommit.sh

Lines changed: 0 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -10,50 +10,6 @@ cd $(dirname $0)
1010

1111
export FORCE_COLOR=true
1212

13-
# Verify every .codex directory mirrors its sibling .claude via child symlinks, so that adding a
14-
# file or folder to a .claude config does not silently leave the sandboxed .codex path behind.
15-
# Only immediate children are checked: a symlinked folder (e.g. .codex/skills) already covers its
16-
# contents, and a .codex that is itself a symlink (the repo root) mirrors .claude inherently.
17-
check_codex_symlinks() {
18-
local repo_root claude_dirs claude_dir codex_dir path name
19-
local -a errors=()
20-
repo_root=$(git rev-parse --show-toplevel)
21-
claude_dirs=$(cd "$repo_root" && git ls-files -- '.claude/*' '*/.claude/*' | sed -E 's#(.*/)?\.claude/.*#\1.claude#' | sort -u)
22-
23-
for claude_dir in $claude_dirs; do
24-
codex_dir="${claude_dir%.claude}.codex"
25-
if [ -L "$repo_root/$codex_dir" ]; then
26-
continue
27-
fi
28-
if [ ! -d "$repo_root/$codex_dir" ]; then
29-
errors+=("missing directory $codex_dir (should mirror $claude_dir)")
30-
continue
31-
fi
32-
while IFS= read -r path; do
33-
name=$(basename "$path")
34-
if [ ! -L "$repo_root/$codex_dir/$name" ] || [ ! "$repo_root/$codex_dir/$name" -ef "$path" ]; then
35-
errors+=("$codex_dir/$name should be a symlink to ../.claude/$name")
36-
fi
37-
done < <(find "$repo_root/$claude_dir" -mindepth 1 -maxdepth 1)
38-
while IFS= read -r path; do
39-
name=$(basename "$path")
40-
if [ ! -e "$repo_root/$claude_dir/$name" ] && [ ! -L "$repo_root/$claude_dir/$name" ]; then
41-
errors+=("$codex_dir/$name is stale; no matching $claude_dir/$name")
42-
fi
43-
done < <(find "$repo_root/$codex_dir" -mindepth 1 -maxdepth 1)
44-
done
45-
46-
if (( ${#errors[@]} > 0 )); then
47-
echo -e "\033[31mError:\033[0m .codex directories are out of sync with their .claude siblings:"
48-
for e in "${errors[@]}"; do echo " - $e"; done
49-
echo "Each entry under a .claude folder needs a sibling symlink in .codex, e.g.:"
50-
echo " (cd <component>/.codex && ln -s ../.claude/<name> <name> && git add <name>)"
51-
return 1
52-
fi
53-
}
54-
55-
check_codex_symlinks
56-
5713
# Get all staged files (excluding deleted), relative to yarn-project
5814
staged_files=$(git diff-index --diff-filter=d --relative --cached --name-only HEAD)
5915

0 commit comments

Comments
 (0)