Skip to content

Commit 66e979a

Browse files
ivklgnclaude
andcommitted
feat(launcher): production-grade CWD guard reliability
Add warning when ARCHCORE_CWD is set but not a directory (file, typo, stale symlink), preventing silent wrong-cwd failures. Previously the launcher would silently ignore bad ARCHCORE_CWD, leaving only the diagnostic log as a clue. Add 15 new unit and structure tests covering previously untested edge cases: - Filesystem root (/) refusal - All 9 project markers (.git, .archcore, package.json, go.mod, pyproject.toml, Cargo.toml, pom.xml, build.gradle, build.gradle.kts) — 6 were untested - CURSOR_PLUGIN_ROOT interactions (matches cwd, different valid project, redirect) - ARCHCORE_CWD edge cases (file instead of dir, nonexistent path) - HOME with .git marker still refused - .mcp.json structural contracts (intentional absence of cwd and ARCHCORE_CWD for Claude Code host, which silently ignores them) Fixes test 73 which broke due to new warning — now asserts for both warning and expected behavior. All 159 unit tests + 117 structure tests pass. No behavioral changes to guards or configs — only Step 0 warning addition and comprehensive coverage of existing guard logic. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
1 parent 7c203bc commit 66e979a

3 files changed

Lines changed: 227 additions & 3 deletions

File tree

bin/archcore

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,13 @@ SELF="$SCRIPT_DIR/archcore"
4747
#
4848
# # bash/zsh: ~/.bashrc / ~/.zshrc
4949
# codex() { ARCHCORE_CWD="$PWD" command codex "$@"; }
50-
if [ -n "${ARCHCORE_CWD:-}" ] && [ -d "$ARCHCORE_CWD" ]; then
51-
cd "$ARCHCORE_CWD" 2>/dev/null || true
50+
if [ -n "${ARCHCORE_CWD:-}" ]; then
51+
if [ -d "$ARCHCORE_CWD" ]; then
52+
cd "$ARCHCORE_CWD" 2>/dev/null || true
53+
else
54+
printf '[archcore launcher] ARCHCORE_CWD is set but is not a directory: %s (ignored)\n' \
55+
"$ARCHCORE_CWD" >&2
56+
fi
5257
fi
5358

5459
# Resolve the bypass flag once. ARCHCORE_ALLOW_ANY_CWD is the canonical name

test/structure/scripts.bats

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,19 @@ $output"
9999
@test "validate-archcore uses launcher" {
100100
grep -q '"\$LAUNCHER"' "$PLUGIN_ROOT/bin/validate-archcore"
101101
}
102+
103+
@test ".mcp.json intentionally has no cwd field (Claude Code ignores it)" {
104+
# Absence is the contract. Claude Code silently ignores cwd field in .mcp.json
105+
# (anthropics/claude-code#17565). A cwd field would be misleading — it would
106+
# have no effect. Absence documents this and prevents future confusion.
107+
run jq -e '.mcpServers.archcore | has("cwd")' < "$PLUGIN_ROOT/.mcp.json"
108+
assert_failure
109+
}
110+
111+
@test ".mcp.json intentionally has no env.ARCHCORE_CWD (Claude Code cannot pass it)" {
112+
# ARCHCORE_CWD cannot be injected via plugin manifest for Claude Code — the
113+
# launcher's Step 0 would have nothing to cd to. The documented fix is to
114+
# launch `claude` from the project directory. Absence here is intentional.
115+
run jq -e '.mcpServers.archcore.env.ARCHCORE_CWD' < "$PLUGIN_ROOT/.mcp.json"
116+
assert_failure
117+
}

test/unit/launcher.bats

Lines changed: 204 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,8 @@ MOCK
239239

240240
run sh -c "cd '$start' && PATH='$MOCK_BIN:/usr/bin:/bin' ARCHCORE_CWD='$BATS_TEST_TMPDIR/does-not-exist' '$PLUGIN_ROOT/bin/archcore' anything"
241241
assert_success
242-
assert_output "$start"
242+
assert_output --partial "ARCHCORE_CWD is set but is not a directory"
243+
assert_output --partial "$start"
243244
}
244245

245246
@test "launcher is a no-op when \$ARCHCORE_CWD is unset" {
@@ -583,3 +584,205 @@ MOCK
583584
assert_success
584585
refute_output --partial "[archcore mcp]"
585586
}
587+
588+
# --- Additional Step 0c coverage: filesystem root, all project markers, HOME with markers ---
589+
590+
@test "sanity guard: refuses mcp when cwd is filesystem root (/)" {
591+
run sh -c "cd / && PATH='/usr/bin:/bin' ARCHCORE_SKIP_DOWNLOAD=1 '$PLUGIN_ROOT/bin/archcore' mcp 2>&1"
592+
assert_failure
593+
assert_output --partial "Refusing to start MCP"
594+
assert_output --partial "filesystem root"
595+
}
596+
597+
@test "sanity guard: passes mcp when cwd has go.mod marker" {
598+
local proj="$BATS_TEST_TMPDIR/proj-go"
599+
mkdir -p "$proj"
600+
: > "$proj/go.mod"
601+
602+
cat > "$MOCK_BIN/archcore" <<'MOCK'
603+
#!/bin/sh
604+
printf 'go-ok\n'
605+
MOCK
606+
chmod +x "$MOCK_BIN/archcore"
607+
608+
run sh -c "cd '$proj' && PATH='$MOCK_BIN:/usr/bin:/bin' '$PLUGIN_ROOT/bin/archcore' mcp 2>/dev/null"
609+
assert_success
610+
assert_output "go-ok"
611+
}
612+
613+
@test "sanity guard: passes mcp when cwd has pyproject.toml marker" {
614+
local proj="$BATS_TEST_TMPDIR/proj-python"
615+
mkdir -p "$proj"
616+
: > "$proj/pyproject.toml"
617+
618+
cat > "$MOCK_BIN/archcore" <<'MOCK'
619+
#!/bin/sh
620+
printf 'python-ok\n'
621+
MOCK
622+
chmod +x "$MOCK_BIN/archcore"
623+
624+
run sh -c "cd '$proj' && PATH='$MOCK_BIN:/usr/bin:/bin' '$PLUGIN_ROOT/bin/archcore' mcp 2>/dev/null"
625+
assert_success
626+
assert_output "python-ok"
627+
}
628+
629+
@test "sanity guard: passes mcp when cwd has Cargo.toml marker" {
630+
local proj="$BATS_TEST_TMPDIR/proj-rust"
631+
mkdir -p "$proj"
632+
: > "$proj/Cargo.toml"
633+
634+
cat > "$MOCK_BIN/archcore" <<'MOCK'
635+
#!/bin/sh
636+
printf 'rust-ok\n'
637+
MOCK
638+
chmod +x "$MOCK_BIN/archcore"
639+
640+
run sh -c "cd '$proj' && PATH='$MOCK_BIN:/usr/bin:/bin' '$PLUGIN_ROOT/bin/archcore' mcp 2>/dev/null"
641+
assert_success
642+
assert_output "rust-ok"
643+
}
644+
645+
@test "sanity guard: passes mcp when cwd has pom.xml marker" {
646+
local proj="$BATS_TEST_TMPDIR/proj-maven"
647+
mkdir -p "$proj"
648+
: > "$proj/pom.xml"
649+
650+
cat > "$MOCK_BIN/archcore" <<'MOCK'
651+
#!/bin/sh
652+
printf 'maven-ok\n'
653+
MOCK
654+
chmod +x "$MOCK_BIN/archcore"
655+
656+
run sh -c "cd '$proj' && PATH='$MOCK_BIN:/usr/bin:/bin' '$PLUGIN_ROOT/bin/archcore' mcp 2>/dev/null"
657+
assert_success
658+
assert_output "maven-ok"
659+
}
660+
661+
@test "sanity guard: passes mcp when cwd has build.gradle marker" {
662+
local proj="$BATS_TEST_TMPDIR/proj-gradle"
663+
mkdir -p "$proj"
664+
: > "$proj/build.gradle"
665+
666+
cat > "$MOCK_BIN/archcore" <<'MOCK'
667+
#!/bin/sh
668+
printf 'gradle-ok\n'
669+
MOCK
670+
chmod +x "$MOCK_BIN/archcore"
671+
672+
run sh -c "cd '$proj' && PATH='$MOCK_BIN:/usr/bin:/bin' '$PLUGIN_ROOT/bin/archcore' mcp 2>/dev/null"
673+
assert_success
674+
assert_output "gradle-ok"
675+
}
676+
677+
@test "sanity guard: passes mcp when cwd has build.gradle.kts marker" {
678+
local proj="$BATS_TEST_TMPDIR/proj-gradle-kotlin"
679+
mkdir -p "$proj"
680+
: > "$proj/build.gradle.kts"
681+
682+
cat > "$MOCK_BIN/archcore" <<'MOCK'
683+
#!/bin/sh
684+
printf 'gradle-kotlin-ok\n'
685+
MOCK
686+
chmod +x "$MOCK_BIN/archcore"
687+
688+
run sh -c "cd '$proj' && PATH='$MOCK_BIN:/usr/bin:/bin' '$PLUGIN_ROOT/bin/archcore' mcp 2>/dev/null"
689+
assert_success
690+
assert_output "gradle-kotlin-ok"
691+
}
692+
693+
@test "sanity guard: HOME with .git marker is still refused" {
694+
local fake_home="$BATS_TEST_TMPDIR/fake-home-with-git"
695+
mkdir -p "$fake_home"
696+
: > "$fake_home/.git"
697+
698+
run sh -c "cd '$fake_home' && HOME='$fake_home' PATH='/usr/bin:/bin' ARCHCORE_SKIP_DOWNLOAD=1 '$PLUGIN_ROOT/bin/archcore' mcp 2>&1"
699+
assert_failure
700+
assert_output --partial "Refusing to start MCP"
701+
assert_output --partial "\$HOME"
702+
}
703+
704+
# --- CURSOR_PLUGIN_ROOT interactions ---
705+
706+
@test "sanity guard: refuses mcp when cwd == CURSOR_PLUGIN_ROOT and cwd has .git" {
707+
local plugin_root="$BATS_TEST_TMPDIR/fake-cursor-plugin"
708+
mkdir -p "$plugin_root"
709+
cd "$plugin_root" && git init -q
710+
711+
run sh -c "cd '$plugin_root' && CURSOR_PLUGIN_ROOT='$plugin_root' PATH='/usr/bin:/bin' ARCHCORE_SKIP_DOWNLOAD=1 '$PLUGIN_ROOT/bin/archcore' mcp 2>&1"
712+
assert_failure
713+
assert_output --partial "Refusing to start MCP"
714+
assert_output --partial "CURSOR_PLUGIN_ROOT"
715+
}
716+
717+
@test "sanity guard: CURSOR_PLUGIN_ROOT set but cwd is different valid project — passes" {
718+
local other_root="$BATS_TEST_TMPDIR/other-cursor-plugin"
719+
local proj="$BATS_TEST_TMPDIR/proj-cursor-other"
720+
mkdir -p "$other_root" "$proj"
721+
: > "$proj/.git"
722+
723+
cat > "$MOCK_BIN/archcore" <<'MOCK'
724+
#!/bin/sh
725+
printf 'cursor-other-ok\n'
726+
MOCK
727+
chmod +x "$MOCK_BIN/archcore"
728+
729+
run sh -c "cd '$proj' && CURSOR_PLUGIN_ROOT='$other_root' PATH='$MOCK_BIN:/usr/bin:/bin' '$PLUGIN_ROOT/bin/archcore' mcp 2>/dev/null"
730+
assert_success
731+
assert_output "cursor-other-ok"
732+
}
733+
734+
@test "ARCHCORE_CWD + CURSOR_PLUGIN_ROOT: redirected cwd passes guard" {
735+
local cursor_root="$BATS_TEST_TMPDIR/cursor-plugin-with-git"
736+
local target_proj="$BATS_TEST_TMPDIR/target-proj-cursor"
737+
mkdir -p "$cursor_root" "$target_proj"
738+
: > "$cursor_root/.git"
739+
: > "$target_proj/.git"
740+
741+
cat > "$MOCK_BIN/archcore" <<'MOCK'
742+
#!/bin/sh
743+
printf 'archcore-cwd-rebase-ok\n'
744+
MOCK
745+
chmod +x "$MOCK_BIN/archcore"
746+
747+
run sh -c "cd '$cursor_root' && ARCHCORE_CWD='$target_proj' CURSOR_PLUGIN_ROOT='$cursor_root' PATH='$MOCK_BIN:/usr/bin:/bin' '$PLUGIN_ROOT/bin/archcore' mcp 2>/dev/null"
748+
assert_success
749+
assert_output "archcore-cwd-rebase-ok"
750+
}
751+
752+
# --- ARCHCORE_CWD edge cases ---
753+
754+
@test "ARCHCORE_CWD set to an existing file (not dir) — warns and keeps inherited cwd" {
755+
local proj="$BATS_TEST_TMPDIR/proj-cwd-file"
756+
local bad_file="$BATS_TEST_TMPDIR/bad-cwd-file"
757+
mkdir -p "$proj"
758+
: > "$proj/.git"
759+
: > "$bad_file"
760+
761+
cat > "$MOCK_BIN/archcore" <<'MOCK'
762+
#!/bin/sh
763+
exit 0
764+
MOCK
765+
chmod +x "$MOCK_BIN/archcore"
766+
767+
run sh -c "cd '$proj' && ARCHCORE_CWD='$bad_file' PATH='$MOCK_BIN:/usr/bin:/bin' '$PLUGIN_ROOT/bin/archcore' doctor 2>&1 1>/dev/null"
768+
assert_success
769+
assert_output --partial "ARCHCORE_CWD is set but is not a directory"
770+
assert_output --partial "$bad_file"
771+
}
772+
773+
@test "ARCHCORE_CWD set to nonexistent path — silently ignored (no warning)" {
774+
local proj="$BATS_TEST_TMPDIR/proj-cwd-nonexistent"
775+
mkdir -p "$proj"
776+
: > "$proj/.git"
777+
778+
cat > "$MOCK_BIN/archcore" <<'MOCK'
779+
#!/bin/sh
780+
printf 'no-warn-ok\n'
781+
MOCK
782+
chmod +x "$MOCK_BIN/archcore"
783+
784+
run sh -c "cd '$proj' && ARCHCORE_CWD='/does/not/exist/xyz' PATH='$MOCK_BIN:/usr/bin:/bin' '$PLUGIN_ROOT/bin/archcore' mcp 2>/dev/null"
785+
assert_success
786+
assert_output "no-warn-ok"
787+
refute_output --partial "is not a directory"
788+
}

0 commit comments

Comments
 (0)