Skip to content

Point Claude runs at path-scoped rules and nested memory - #2076

Merged
ppXD merged 1 commit into
mainfrom
fix/point-claude-runs-at-scoped-rules-and-nested-memory
Oct 6, 2026
Merged

ppXD merged 1 commit into
mainfrom
fix/point-claude-runs-at-scoped-rules-and-nested-memory

Conversation

@ppXD

@ppXD ppXD commented Oct 6, 2026

Copy link
Copy Markdown
Owner

Summary

  • Pointer rules. Three kinds of memory never reach the model under the pin:

    • a rule scoped by paths: (no --add-dir loads it);
    • nested memory past the in-place budget (it is not added);
    • memory loaded in place, for an Explore or Plan subagent, which the CLI starts without project memory.

    The pinned 2.1.263 still attaches a user rule under CLAUDE_CONFIG_DIR/rules whose paths: match a file its Read tool opens, in subagents too. Each build therefore writes rules/codespace-repository-NNN.md (ClaudeWorkspaceMemory.Pointers.cs):

    • one per scoped repository rule, its globs rebased onto the cwd with gitignore anchoring (ClaudeRuleScope.Rebase);
    • one per nested directory that holds memory, globbed /<dir>. Past the budget its sentence says the memory was not preloaded; in place, it says to read the memory only if it is not already in context.
  • No repository byte in the config home.

    • A pointer is runner text: absolute paths of [A-Za-z0-9._+-/] in backticks, never an @, and globs re-emitted from a safe set.
    • A memory file with an unsafe name is left out of its directory's pointer alone, with a notice.
    • Every pointer is read back through the port of the CLI's frontmatter parse and dropped unless that gives exactly the globs written. A glob holding ---, for example from {-,b}--x, is refused there.
  • Bounds.

    • Each pointer is at most 4 KiB, naming its directory's memory files while they fit and counting the rest.
    • All pointers together are at most 128 KiB; at most 128 pointers are written and 128 directories checked.
    • The directory a pointer points into passes the same guard, on the same per-build budget.
    • The walk continues past the in-place budget to find every scoped rule and every directory to point at.

Test plan

  • Unit: the full suite passes (12050 passed, 1 skipped). New cases:
    • a 10000-rule directory's pointer stays under 4 KiB and counts the rest;
    • three 6300-rule directories with 922-byte paths keep the spec inside the launch frame (NativeLaunchProtocol.FitsTheFrame);
    • the 128 KiB total and its notice;
    • an unsafe rule name left out alone;
    • a primary-repository rule linked into the sibling gets no pointer;
    • a frontmatter longer than 4 KiB still gets its pointer;
    • {-,b}--x refused by the read-back;
    • 128 directories of chain links checked within the build budget, shared or not;
    • in-place directories pointed at;
    • bounds pinned (Rule 8).
  • Unit, mutations: each of these turns at least one case red:
    • naming every file;
    • no total bound;
    • an unsafe name refusing the whole pointer;
    • read-back off;
    • no in-place pointers;
    • a fresh budget per directory.
  • Integration (Postgres): RealHarnessWorkspaceMemoryTests 7/7. The large-tree arm now has pkg-out/CLAUDE.md linked outside: it gets no pointer and one notice. With the pointer guard off, the arm fails.
  • E2E on macOS, real Claude 2.1.263 and Codex 0.142.2: RepositoryConfigE2ETests 14/14. The workflow's repository-config gate, run against the TRX, prints All 14 repository-config E2E cases and 14 markers ran and passed.
    • The differential corpus adds gen/, src/**/** and a frontmatter past 4 KiB. It now requires one pointer per scoped rule, none for the ** rule, and none in the first request. Each of these fails it:
      • anchoring on a trailing slash;
      • dropping the /** doubling;
      • reading a lone ** as scoped;
      • classifying from the head only.
    • The over-budget arm adds two directories under /etc, where bwrap binds them: one whose CLAUDE.md links outside, one whose scoped rule imports an outside file. Reads below them attach nothing, the launch names both, and no outside text reaches a request. With the pointer guard off, the arm fails.
    • An_explore_subagent_is_pointed_at_nested_memory_loaded_in_place: unpinned, the CLI attaches pkg/CLAUDE.md to an Explore subagent's read below pkg/. Pinned, the subagent never sees the in-place memory, and its read attaches the pointer. With in-place pointers off, the arm fails.
    • ScriptedModelUpstream gains a subagent script and an opt-in classifier verdict, so plan mode lets Explore start.
    • The nested-in-place arms require three pointers per repository and none in the first request.
  • Review bench:
    • 128 small directories of chain links: 38.2 s → 0.5 s;
    • 128 directories naming 16000 imports: 59.1 s → 2.1 s, with the budget notice;
    • three 6300-rule directories: spec JSON 17.7 MB → 20 KB, largest pointer 4034 bytes.
  • CI root sandbox lane: floor 107, new marker nested-subagent-pointer claude-code single-repo Confined; the gate now prints its counts from the tuples it checks
  • CI non-root lane: 19 arms, marker [repo-config-e2e] ran non-root scoped-rule-pointer claude-code single-repo Standard uid=1654

@ppXD
ppXD force-pushed the fix/load-nested-claude-memory-in-place branch from a3230e4 to a07d31e Compare October 6, 2026 18:48
@ppXD
ppXD force-pushed the fix/point-claude-runs-at-scoped-rules-and-nested-memory branch from 0af35b9 to a7c0bfb Compare October 6, 2026 18:49
@ppXD
ppXD changed the base branch from fix/load-nested-claude-memory-in-place to main October 6, 2026 22:25
@ppXD
ppXD force-pushed the fix/point-claude-runs-at-scoped-rules-and-nested-memory branch from a7c0bfb to a49d64e Compare October 6, 2026 22:25
No --add-dir loads a .claude/rules file scoped by paths:, and the
settings pin shuts the route by which the unpinned CLI attached one
once its Read tool opened a file the rule covers. Nested memory past
the in-place budget was lost the same way, and memory loaded in place
never reaches an Explore or Plan subagent, which the CLI starts
without project memory but to which, unpinned, it attached a
directory's memory once the subagent read below it.

The pinned 2.1.263 still attaches a rule of the user's own, under
CLAUDE_CONFIG_DIR/rules, whose paths: match a file it reads, matched
relative to the run's cwd, subagents included. Each build now writes
one such pointer rule per scoped repository rule, in every directory
the walk finds, and one per nested directory that holds memory, as
rules/codespace-repository-NNN.md in walk order. A pointer's globs are
the repository rule's own as the CLI reads them, rebased onto the cwd
the way gitignore anchors them: as written when the rule's directory
is the cwd, below that directory when a slash anchors the glob, at any
depth below it otherwise, a negation kept. A directory's pointer
covers everything below it; past the budget its sentence says the
memory was not preloaded, in place to read it only if it is not
already in context.

No repository byte reaches the config home. The user scope reads
external imports, so a copied rule would hand the model whatever its
@-lines name before the first request. A pointer names absolute paths
of letters, digits and . _ + - / in backticks, never an @ anywhere, and
re-emits only globs of those characters plus * ? and one leading !;
anything else gets no pointer and a launch notice, and a memory file
whose name holds anything else is left out of its directory's pointer
alone. Each pointer is read back through the port of the CLI's own
frontmatter parse and kept only when that gives exactly the globs
written, so none is ever unconditional, which would load it up front
at the user's authority; a glob holding ---, which brace expansion can
make, would close the frontmatter early and is refused.

A pointer is at most 4 KiB, naming its directory's memory files while
they fit and counting the rest, and all pointers together at most
128 KiB, at most 128 of them: named one by one, thousands of rules
made a pointer megabytes long, which ended the run on the read that
attached it and could push the launch past its 16 MiB frame. A
directory a pointer points into passes the same outside-link guard as
an added one, against the same per-build budget, so 128 small
directories of links no longer hold the build for minutes.

The walk now continues past the budget so it finds every scoped rule
and every directory to point at, and the over-budget notice says the
run is pointed at that memory instead.

Against the real binary a scoped rule's pointer attaches only on a
read its glob covers, single- and multi-repo, never on another
repository's read, and carries none of the rule's text or imports;
copying the rule into the config home instead hands the run's MCP
token to the model. A differential arm runs one corpus of glob shapes,
a trailing slash, a doubled /** and a frontmatter past 4 KiB among
them, through the unpinned CLI and the production run, requires the
same rules to attach on the same reads, one pointer per scoped rule
and none in the first request; dropping an unanchored glob's **/,
anchoring on a trailing slash, the /** doubling or the ** rule's
unconditional reading fails it. Past the budget, a read below one
directory attaches that directory's pointer and no other, and none
below a directory whose memory reaches outside. An Explore subagent's
read below a directory loaded in place attaches its pointer, where the
unpinned CLI attached the memory itself.
@ppXD
ppXD merged commit 7c98fbb into main Oct 6, 2026
6 checks passed
@ppXD
ppXD deleted the fix/point-claude-runs-at-scoped-rules-and-nested-memory branch October 6, 2026 22:26
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.

1 participant