Skip to content

fix(check): honor --base scope when workspace is a repo subdirectory#130

Merged
Connorrmcd6 merged 1 commit into
Connorrmcd6:mainfrom
laurigates:upstream-pr/fix-base-subdir-scope
Jun 15, 2026
Merged

fix(check): honor --base scope when workspace is a repo subdirectory#130
Connorrmcd6 merged 1 commit into
Connorrmcd6:mainfrom
laurigates:upstream-pr/fix-base-subdir-scope

Conversation

@laurigates

@laurigates laurigates commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Severity: HIGH — silent gate bypass

surf check --base <ref> can pass (exit 0) despite real, anchored drift whenever
surf.toml lives in a subdirectory of the git repo (the monorepo / nested-package
layout). This is the exact diff-scoped mode action.yml recommends for PR gating, so the
bypass lands squarely on the CI path.

Root cause

git::changed_files runs git diff --name-only <base>. Without --relative, git emits
repo-root-relative paths (e.g. proj/src/x.rs), while Anchor.file is
workspace-root-relative (src/x.rs). The two sets never intersect, so Scope::includes
filters out every claim → zero claims checked → clean exit.

Reproduction (before fix)

repo/
  proj/
    surf.toml
    src/m.rs        # anchored `add`, hash stored for `a + b`
    hubs/a.md

With a + b committed and the working tree changed to a - b:

  • surf check → exit 1 (DIVERGED) ✅
  • surf check --base HEAD → exit 0 ("all anchored spans match") ❌ bypass

When the workspace is the repo root, both report exit 1 — which is why the existing
base_scope_limits_to_changed test never caught it.

Fix

Add --relative to the diff so git emits workspace-root-relative paths. It is a no-op when
the workspace is the repo root, and it additionally drops changes outside the workspace
(which can never be anchored). One-line change in surf-cli/src/git.rs.

Test

Adds base_scope_works_when_workspace_is_a_repo_subdir, which initializes the repo at the
parent of the workspace and asserts a working-tree drift still fails check --base. It
fails without the fix (left == right assertion: 0 vs 1) and passes with it.

cargo clippy --all-targets clean; full suite green (180 tests).

Part of an ongoing security/correctness review of Surface.

🤖 Generated with Claude Code

)

* fix(check): honor --base scope when workspace is a repo subdirectory

git diff --name-only reports repo-root-relative paths, but anchor files
are workspace-root-relative. When surf.toml lives in a subdirectory of
the git repo, the two never intersect, so check --base silently scoped
the gate to zero claims and exited 0 despite real drift — the exact
diff-scoped mode action.yml recommends for PR gating.

Pass --relative so git emits workspace-root-relative paths (a no-op when
the workspace is the repo root). Adds a regression test that fails
without the fix.

https://claude.ai/code/session_01T3Z35bgg6cvWzBDfyh6pQ8

* style: rustfmt subdir-base regression test

* docs(hub): update changed_files claim for workspace-relative scoping

The --base fix changes changed_files to emit workspace-root-relative
paths (git diff --relative). Surface's own dogfood gate flagged the
stale hash + prose, which still described repo-relative paths. Update
the claim text and re-stamp the hash.

(cherry picked from commit b20de8b)
@Connorrmcd6 Connorrmcd6 merged commit 48aac16 into Connorrmcd6:main Jun 15, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants