From 2acf86e06b11f3a1adbef91aa87f9aebd482faf2 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Wed, 27 May 2026 15:03:14 -0300 Subject: [PATCH] chore: fix claude and codex symlinking tests 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. --- .claude/bootstrap.sh | 4 ++- .claude/tests/agents_symlink_test | 40 +++++++++++++++++++++++----- yarn-project/precommit.sh | 44 ------------------------------- 3 files changed, 36 insertions(+), 52 deletions(-) diff --git a/.claude/bootstrap.sh b/.claude/bootstrap.sh index 021f5270f3fb..078ddd1c22a2 100755 --- a/.claude/bootstrap.sh +++ b/.claude/bootstrap.sh @@ -4,7 +4,9 @@ # `test`. Keeps hook scripts and their tests as a self-contained component. source $(git rev-parse --show-toplevel)/ci3/source_bootstrap -hash=$(cache_content_hash ^.claude) +# Hash everything agents_symlink_test inspects, not just .claude/: the .codex mirrors and the root +# AGENTS.md/CLAUDE.md symlinks live outside .claude/, so a change there must still rerun the test. +hash=$(cache_content_hash "^.*.claude" "^.*.codex" "^AGENTS.md" "^CLAUDE.md") function test_cmds { # source_base cd's us into .claude/, so glob relative-to-here, but emit paths diff --git a/.claude/tests/agents_symlink_test b/.claude/tests/agents_symlink_test index 48a13ff77f16..4c3f2244bcf1 100755 --- a/.claude/tests/agents_symlink_test +++ b/.claude/tests/agents_symlink_test @@ -82,17 +82,43 @@ while IFS= read -r dir; do "$ROOT"/noir/noir-repo/*) continue;; esac codex="$(dirname "$dir")/.codex" - if [[ ! -L "$codex" ]]; then - fail "${codex#$ROOT/} is missing or is not a symlink to .claude" + rel=${codex#$ROOT/} + # A .codex may either be a symlink to its sibling .claude (the repo root keeps this form), or a + # real directory whose immediate children symlink into .claude (the per-package form, required + # because a sandbox cannot bind-mount a path that is itself a symlink). Either way Codex must see + # exactly the same contents as .claude. + if [[ -L "$codex" ]]; then + resolved=$(cd "$(dirname "$codex")" && cd "$(readlink "$codex")" 2>/dev/null && pwd -P) || resolved="" + claude_resolved=$(cd "$dir" && pwd -P) + if [[ "$resolved" != "$claude_resolved" ]]; then + fail "$rel is a symlink to ${resolved:-?}, expected its sibling .claude ($claude_resolved)" + else + pass "$rel -> .claude" + fi continue fi - resolved=$(cd "$(dirname "$codex")" && cd "$(readlink "$codex")" 2>/dev/null && pwd -P) || resolved="" - claude_resolved=$(cd "$dir" && pwd -P) - if [[ "$resolved" != "$claude_resolved" ]]; then - fail "${codex#$ROOT/} resolves to $resolved, expected $claude_resolved" + if [[ ! -d "$codex" ]]; then + fail "$rel is missing (expected a directory mirroring .claude via child symlinks)" continue fi - pass "${codex#$ROOT/}" + codex_ok=1 + # Forward: every entry in .claude must have a matching symlink in .codex. + while IFS= read -r child; do + name=$(basename "$child") + if [[ ! -L "$codex/$name" || ! "$codex/$name" -ef "$child" ]]; then + fail "$rel/$name should be a symlink to ../.claude/$name" + codex_ok=0 + fi + done < <(find "$dir" -mindepth 1 -maxdepth 1) + # Reverse: every entry in .codex must correspond to a .claude entry (no stale or dangling links). + while IFS= read -r entry; do + name=$(basename "$entry") + if [[ ! -e "$dir/$name" && ! -L "$dir/$name" ]]; then + fail "$rel/$name is stale; no matching .claude/$name" + codex_ok=0 + fi + done < <(find "$codex" -mindepth 1 -maxdepth 1) + (( codex_ok )) && pass "$rel" done < <(find "$ROOT" -type d -name .claude -not -path "$ROOT/noir/*" -not -path "$ROOT/**/node_modules/*") echo diff --git a/yarn-project/precommit.sh b/yarn-project/precommit.sh index 1f5bd7c8420a..4407f3045225 100755 --- a/yarn-project/precommit.sh +++ b/yarn-project/precommit.sh @@ -10,50 +10,6 @@ cd $(dirname $0) export FORCE_COLOR=true -# Verify every .codex directory mirrors its sibling .claude via child symlinks, so that adding a -# file or folder to a .claude config does not silently leave the sandboxed .codex path behind. -# Only immediate children are checked: a symlinked folder (e.g. .codex/skills) already covers its -# contents, and a .codex that is itself a symlink (the repo root) mirrors .claude inherently. -check_codex_symlinks() { - local repo_root claude_dirs claude_dir codex_dir path name - local -a errors=() - repo_root=$(git rev-parse --show-toplevel) - claude_dirs=$(cd "$repo_root" && git ls-files -- '.claude/*' '*/.claude/*' | sed -E 's#(.*/)?\.claude/.*#\1.claude#' | sort -u) - - for claude_dir in $claude_dirs; do - codex_dir="${claude_dir%.claude}.codex" - if [ -L "$repo_root/$codex_dir" ]; then - continue - fi - if [ ! -d "$repo_root/$codex_dir" ]; then - errors+=("missing directory $codex_dir (should mirror $claude_dir)") - continue - fi - while IFS= read -r path; do - name=$(basename "$path") - if [ ! -L "$repo_root/$codex_dir/$name" ] || [ ! "$repo_root/$codex_dir/$name" -ef "$path" ]; then - errors+=("$codex_dir/$name should be a symlink to ../.claude/$name") - fi - done < <(find "$repo_root/$claude_dir" -mindepth 1 -maxdepth 1) - while IFS= read -r path; do - name=$(basename "$path") - if [ ! -e "$repo_root/$claude_dir/$name" ] && [ ! -L "$repo_root/$claude_dir/$name" ]; then - errors+=("$codex_dir/$name is stale; no matching $claude_dir/$name") - fi - done < <(find "$repo_root/$codex_dir" -mindepth 1 -maxdepth 1) - done - - if (( ${#errors[@]} > 0 )); then - echo -e "\033[31mError:\033[0m .codex directories are out of sync with their .claude siblings:" - for e in "${errors[@]}"; do echo " - $e"; done - echo "Each entry under a .claude folder needs a sibling symlink in .codex, e.g.:" - echo " (cd /.codex && ln -s ../.claude/ && git add )" - return 1 - fi -} - -check_codex_symlinks - # Get all staged files (excluding deleted), relative to yarn-project staged_files=$(git diff-index --diff-filter=d --relative --cached --name-only HEAD)