fix(v2): use in-tree symlink for global CLAUDE.md @import
Claude Code's @-import directive only follows paths inside the project memory tree (cwd + ancestors). Both `@/workspace/global/CLAUDE.md` and `@../global/CLAUDE.md` are silently ignored because `/workspace/global` is outside `/workspace/agent` (the cwd). The import line is parsed but the content is never loaded — validated with a sentinel passphrase test against a live container. Fix: drop a `.claude-global.md` symlink into each group's dir pointing at `/workspace/global/CLAUDE.md`. The link path is absolute on container terms (dangling on host, valid via the /workspace/global mount) and the symlink file itself is inside cwd, so Claude's @-import is happy. The group's CLAUDE.md imports via `@./.claude-global.md`. - src/group-init.ts: initGroupFilesystem now drops the symlink (idempotent, uses lstat so existsSync doesn't trip on the dangling target on the host). Default CLAUDE.md body uses `@./.claude-global.md`. - scripts/migrate-group-claude-md.ts: creates the symlink for existing groups and rewrites any broken `@/workspace/global/CLAUDE.md` or `@../global/CLAUDE.md` import line to `@./.claude-global.md`. - groups/main/CLAUDE.md: migration rewrote the import. Validated: live container with the symlinked import correctly surfaces global CLAUDE.md content (passphrase `quinoa-submarine-42` added to global, retrieved via claude -p, removed). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,17 @@ import { DATA_DIR, GROUPS_DIR } from './config.js';
|
||||
import { log } from './log.js';
|
||||
import type { AgentGroup } from './types.js';
|
||||
|
||||
const GLOBAL_CLAUDE_IMPORT = '@/workspace/global/CLAUDE.md';
|
||||
// Container path where groups/global is mounted. The symlink we drop
|
||||
// into each group's dir resolves to this target inside the container.
|
||||
// It's a dangling symlink on the host — that's fine, host tools don't
|
||||
// follow it and the container mount makes it valid at read time.
|
||||
const GLOBAL_MEMORY_CONTAINER_PATH = '/workspace/global/CLAUDE.md';
|
||||
|
||||
// Symlink name inside the group's dir. Claude Code's @-import only
|
||||
// follows paths inside cwd, so we can't reference /workspace/global
|
||||
// directly — we symlink into the group dir and import the symlink.
|
||||
export const GLOBAL_MEMORY_LINK_NAME = '.claude-global.md';
|
||||
export const GLOBAL_CLAUDE_IMPORT = `@./${GLOBAL_MEMORY_LINK_NAME}`;
|
||||
|
||||
const DEFAULT_SETTINGS_JSON =
|
||||
JSON.stringify(
|
||||
@@ -41,6 +51,23 @@ export function initGroupFilesystem(group: AgentGroup, opts?: { instructions?: s
|
||||
initialized.push('groupDir');
|
||||
}
|
||||
|
||||
// groups/<folder>/.claude-global.md — symlink into the group dir so
|
||||
// Claude Code's @-import can follow it. Uses lstat to avoid tripping
|
||||
// existsSync on a dangling symlink (target only resolves inside the
|
||||
// container).
|
||||
const globalLinkPath = path.join(groupDir, GLOBAL_MEMORY_LINK_NAME);
|
||||
let linkExists = false;
|
||||
try {
|
||||
fs.lstatSync(globalLinkPath);
|
||||
linkExists = true;
|
||||
} catch {
|
||||
/* missing — recreate */
|
||||
}
|
||||
if (!linkExists) {
|
||||
fs.symlinkSync(GLOBAL_MEMORY_CONTAINER_PATH, globalLinkPath);
|
||||
initialized.push('.claude-global.md');
|
||||
}
|
||||
|
||||
// groups/<folder>/CLAUDE.md — written once, then owned by the group
|
||||
const claudeMdFile = path.join(groupDir, 'CLAUDE.md');
|
||||
if (!fs.existsSync(claudeMdFile)) {
|
||||
|
||||
Reference in New Issue
Block a user