Skip to content

feat(server): read Claude Code conventions behind a compatibility setting - #304

Open
nfertigsw wants to merge 1 commit into
co-l:developfrom
nfertigsw:feat/claude-code-compat
Open

feat(server): read Claude Code conventions behind a compatibility setting#304
nfertigsw wants to merge 1 commit into
co-l:developfrom
nfertigsw:feat/claude-code-compat

Conversation

@nfertigsw

@nfertigsw nfertigsw commented Aug 31, 2026

Copy link
Copy Markdown

Summary

OpenFox reads CLAUDE.md as one of its instruction filenames, which looks like Claude Code support but stops well short of it. Claude Code keeps skills in .claude/skills, memory in .claude/CLAUDE.md and ~/.claude/CLAUDE.md, and composes memory files with @file imports. None of that was visible, so someone arriving from Claude Code silently lost most of their setup.

Measured on a real machine before this PR:

  • Of 14 skills in ~/.claude/skills, only the 8 that happened to be symlinks into ~/.agents/skills were discovered. The 6 real directories were invisible.
  • ~/.claude/CLAUDE.md was never read — and its entire content was @RTK.md, so even reading it would have contributed nothing without import expansion.

Full design rationale, precedence tables and edge cases: docs/DESIGN-CLAUDE-CODE-COMPAT.md (added by this PR).

The setting

compat.claudeCode, tri-state, in Settings → Advanced (sits between "Speculative Cache Warming" and "Network"):

Value Behaviour
auto (default) on when the project holds a .claude/ directory or a CLAUDE.md
true always on
false always off

Tri-state rather than a toggle because a boolean cannot express "decide per project" — which is the behaviour that makes the feature invisible when it should be. A project using AGENTS.md is untouched and nothing new enters its prompt.

What it turns on

  • Skills from ~/.claude/skills and <project>/.claude/skills, as new sources global-claude / project-claude. No parser work was needed — loadPortableSkills already reads <dir>/SKILL.md with name / description frontmatter, which is exactly Claude Code's format; only the paths were missing. Each Claude root sits just after its .agents counterpart and before OpenFox's own, so an OpenFox skill always wins an id collision.
  • Memory files: ~/.claude/CLAUDE.md prepended before the tree walk (so user-level memory has the lowest priority and any project file overrides it) and <dir>/.claude/CLAUDE.md at each level of the existing walk.
  • @file imports inside instruction files: resolved against the importing file, ~/ expanded, depth capped at 5, each path canonicalised through realpath and inlined once so cycles terminate, skipped inside fenced blocks and inline code spans, and left exactly as written when the path does not resolve — so @mention and user@example.com survive untouched.

Always on, regardless of the setting

Instruction files are deduplicated by content hash (SHA-256 over trimmed content). Repositories very commonly ship AGENTS.md and CLAUDE.md as copies or symlinks of one another, and both were being sent to the model. That is a correctness bug predating this feature and unrelated to Claude Code, so it is not gated. loadInstructionFiles now reads each file once and returns its content, so getAllInstructions no longer re-reads them and its injected-file list reports only what was actually injected.

One drive-by

The "reached through multiple paths" skill diagnostic now fires only when a directory the user picked (selected) overlaps another root. Sharing one library between both tools by symlinking .claude/skills.agents/skills is common, and it produced one diagnostic line per skill that named a real overlap the user cannot act on — 8 lines of noise on the machine above. The existing test covering a user-selected symlinked directory still passes unchanged.

Deliberately out of scope

Still invisible to OpenFox, and called out as such in the design doc: .claude/agents/*.md, .claude/commands/*.md, .mcp.json, .claude/settings.json, and plugin skills under ~/.claude/plugins.

What to test

  • Nothing changes on an ordinary project. Open a repo with only AGENTS.md, leave the setting on auto. Settings → Skills shows the same list as before; the prompt gains nothing. (OpenFox's own repo is such a project — it has no .claude/ and no CLAUDE.md.)
  • Auto-detection. Open a project that has a .claude/ directory or a CLAUDE.md, still on auto. Skills from .claude/skills now appear under Shared in the Skills modal, and ~/.claude/CLAUDE.md shows up among the injected instruction files.
  • Forcing it. Settings → Advanced → set "Always on". Your ~/.claude/skills entries appear on any project. Set "Always off" on a Claude Code project and they disappear again.
  • Imports. Put @some-file.md on its own line in a CLAUDE.md and confirm the file's content is inlined, prefixed with Instructions from: <path>. Then check that @notafile, an e-mail address, and an @import written inside a fenced code block are all left alone.
  • Deduplication. Put identical content in AGENTS.md and CLAUDE.md at a project root — the text should reach the model once, not twice, whatever the setting is.
  • Precedence. Give a skill the same id in .claude/skills and .openfox/skills; the OpenFox one must win.

Tests

25 added, covering detection and all three tri-state modes, both .claude memory locations, import resolution / cycles / depth / code-span exclusion, content deduplication, and .claude skill discovery with precedence and diagnostics. Existing instruction tests now pin the compat flag off so the suite never reaches into the developer's own ~/.claude.

Local run on WSL: npm run typecheck ✅, eslint src/ web/src/ ✅ (0 issues), npm run duplicate ✅ (0 clones), prettier --check ✅, e2e suite ✅ (335 passed), unit suite 4992 passed with one failure — inspect-proxy.test.ts > handles unreachable target gracefully. That test proxies to http://127.0.0.1:1 and expects a refused connection; on this machine the connect hangs instead (verified independently: a raw TCP connect to port 1 times out rather than being refused), so the proxy never answers and the test hits its 15s cap. It fails identically on a clean checkout without this branch. CI on ubuntu-latest should be unaffected.

AI-Enhanced Development

Tell what models helped shape this PR:

  • AI Models: Claude Opus 5 (1M context), via Claude Code

Cache Impact

Does this PR affect anything cached — system prompts, tool definitions, skills, or other context?

  • Yes. Both cached inputs change when compatibility is active: the instruction block can gain ~/.claude/CLAUDE.md plus any inlined @file imports and loses duplicated AGENTS.md/CLAUDE.md text, and the skill metadata list can gain .claude skills. Both already feed computeDynamicContextHash (instruction content + sorted skill ids), so flipping the setting or opening a Claude Code project invalidates the prompt cache through the existing mechanism — no new cache key was needed. With the default auto on a project without .claude/ or CLAUDE.md, the only reachable change is the deduplication, which fires solely when two instruction files carry byte-identical content.

…ting

OpenFox only read CLAUDE.md as a plain instruction file. Projects coming from
Claude Code keep their skills in `.claude/skills`, their memory in
`.claude/CLAUDE.md` and `~/.claude/CLAUDE.md`, and compose memory files with
`@file` imports — none of which were picked up.

- Discover skills from `~/.claude/skills` and `<project>/.claude/skills`
  (sources `global-claude` / `project-claude`). The portable SKILL.md format was
  already compatible; only the paths were missing. OpenFox skills still win on
  id collisions.
- Read `~/.claude/CLAUDE.md` (user memory, lowest priority) and
  `<dir>/.claude/CLAUDE.md` at every level of the instruction tree walk.
- Expand `@file` imports inside instruction files: resolved against the
  importing file, `~/` expanded, depth capped at 5, each file inlined once so
  cycles terminate, skipped inside code fences and code spans, and left
  untouched when the path does not resolve (so `@mention` and e-mail addresses
  survive).
- Deduplicate instruction files by content hash. Repositories commonly ship
  AGENTS.md and CLAUDE.md as copies or symlinks of one another, which was
  sending the same text to the model twice. This one is unconditional — it is a
  correctness fix, not a compatibility behaviour.

All of the above except the deduplication sits behind the tri-state
`compat.claudeCode` setting (Settings -> Advanced): `auto` (default) turns it on
when the project holds a `.claude/` directory or a `CLAUDE.md`, `true` forces it
on, `false` off.

Overlap diagnostics now only fire when a user-picked directory overlaps another
root; two automatic roots symlinked into each other (a common
`.claude/skills` -> `.agents/skills` setup) is not actionable and was pure noise.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@nfertigsw
nfertigsw force-pushed the feat/claude-code-compat branch from c6f3030 to ea882ce Compare August 31, 2026 21:05
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