google-gemini / google-gemini/gemini-cli
Flat memory import format ignores maxDepth (tree format honors it; CLAUDE.md-parity semantics violated)
- Dominant language
- TypeScript
- Stars
- 107k
- Forks
- 14.6k
- Avg merge
- 2d 3h
- Merged PRs (30d)
- 45
Description
### Summary
The `flat` memory import format (`memory.importFormat: "flat"`) does not enforce the import depth limit that the `tree` format enforces. `processFlat` in `packages/core/src/utils/memoryImportProcessor.ts` accepts a `depth` parameter but never compares it against `importState.maxDepth`, so a deep `@import` chain is processed to unlimited depth in flat mode while the same chain truncates at depth 5 in tree mode.
This contradicts the project's own stated semantics: #2185 (which introduced this feature via #2230) explicitly targets CLAUDE.md parity, and the Claude Code memory docs it links specify imports "recurse to a depth of 5". The tree branch of the same function implements exactly that check; the flat branch added later omitted it.
### Repro
Create a 12-file chain where each file imports the next:
```bash
dir=$(mktemp -d); cd "$dir"
for i in $(seq 0 11); do
next=""; [ $i -lt 11 ] && next=" @f$((i+1)).md"
printf "CONTENT-F%s%s\n" "$i" "$next" > "f$i.md"
done
```
Run `processImports(root, dir, false, undefined, undefined, fmt)` for both formats:
| format | unique imported files present in output |
|---|---|
| `tree` | 6 (`f0`..`f5`, honoring `maxDepth=5`) |
| `flat` (main, unpatched) | **12** — full chain, no depth cap |
Test (fails on main, passes with fix below):
```ts
it('flat format honors maxDepth=5 exactly like tree format', async () => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'depthchain-'));
const N = 12;
for (let i = 0; i < N; i++) {
const next = i + 1 < N ? ` @f${i + 1}.md` : '';
await fs.writeFile(path.join(dir, `f${i}.md`), `CONTENT-F${i}${next}\n`);
}
const root = await fs.readFile(path.join(dir, 'f0.md'), 'utf-8');
const tree = await processImports(root, dir, false, undefined, undefined, 'tree');
const flat = await processImports(root, dir, false, undefined, undefined, 'flat');
expect(new Set(tree.content.match(/CONTENT-F\d+/g)).size).toBe(6);
expect(new Set(flat.content.match(/CONTENT-F\d+/g)).size).toBe(6);
});
```
Verified against current main (`5411f113`): fails before the patch, passes after. Existing `memoryImportProcessor.test.ts` + `memoryDiscovery.test.ts` suites (56 tests) still pass with the fix.
### Root cause
In the `flat` branch of `processImports`, `processFlat(fileContent, fileBasePath, filePath, depth)` receives `depth` but never checks it. The only cycle protection is the `processedFiles` path set, which prevents infinite loops on repeated path strings but places no bound on chain depth. The `tree` branch guards at function entry with `if (importState.currentDepth >= importState.maxDepth)`, matching the intended CLAUDE.md-parity behavior from #2185.
Practical impact: a user (or a repo's checked-in GEMINI.md) relying on the documented depth-5 semantics gets arbitrarily deep context expansion in flat mode — unbounded token consumption in the model context from an import graph that should have been truncated.
### Fix
One guard in `processFlat`, mirroring the tree branch (using `>` so the file at depth == maxDepth is included with unresolved imports, exactly like tree mode):
```diff
--- a/packages/core/src/utils/memoryImportProcessor.ts
+++ b/packages/core/src/utils/memoryImportProcessor.ts
@@ -227,6 +227,16 @@
async function processFlat(
fileContent: string,
fileBasePath: string,
filePath: string,
depth: number,
) {
+ // Honor the same max depth limit as the tree format
+ if (depth > importState.maxDepth) {
+ if (debugMode) {
+ logger.warn(
+ `Maximum import depth (${importState.maxDepth}) reached at ${filePath}. Stopping flat import processing.`,
+ );
+ }
+ return;
+ }
+
// Normalize the file path to ensure consistent comparison
const normalizedPath = path.normalize(filePath);
```
Verification with the fix applied:
- Depth-chain repro: flat now yields 6 unique files, matching tree.
- Symlink self-import control still terminates correctly (unchanged).
- Full `memoryImportProcessor.test.ts` + `memoryDiscovery.test.ts`: 56/56 pass.
Happy to open a PR with the guard plus the regression test if maintainers want it.
---
*Side note from the filer: I work with AI coding agents daily and run a small service (FreshContext Pack) that fixes stale-doc/deprecated-API debt in AGENTS.md-style context files — https://deploy-foorge-team.vercel.app/sample-fresh-context-pack.html. No action needed on that; the issue above stands on its own.*
Contributor guide
Research direction
Start in packages/core/src/utils/memoryImportProcessor.ts, comparing processFlat with the tree branch and its max-depth handling. Add or update the regression test in memoryImportProcessor.test.ts using the 12-file import chain, then run the memoryImportProcessor.test.ts and memoryDiscovery.test.ts suites. Done means flat and tree each include six files at maxDepth 5 and all 56 tests pass.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- cli
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 90/100