Summary
On Linux with CodeGraphContext 0.5.1 (pipx, Python 3.12.3), cgc watch monitoring a large mixed Go/frontend repository hit concurrent file-change callback errors:
RuntimeError: dictionary changed size during iteration
The exception originated while resolving function calls via a shared imports_map. In a separate later watcher run, initial synchronization remained unfinished for 3h 39m while memory usage climbed dramatically, requiring a manual systemd stop.
The concurrency exception and memory growth were observed on the same machine; I have not proved that the former directly caused the latter.
Environment
- OS: Linux, user-level systemd service
- CGC:
CodeGraphContext 0.5.1, installed with pipx
- Python:
3.12.3
- Backend:
falkordb (local worker / RedisLite)
- Invocation:
cgc watch, working directory a large, actively edited Go + Vue/TypeScript repository
- Configuration:
ENABLE_AUTO_WATCH=false
IGNORE_DIRS=node_modules,venv,.venv,env,.env,dist,build,target,out,.git,.idea,.vscode,__pycache__
IGNORE_HIDDEN_FILES=true
INDEX_SOURCE=true
MAX_FILE_SIZE_MB=10
PARALLEL_WORKERS=4
- The repository also has a
.cgcignore excluding dependencies, build outputs, coverage files, logs, database files and common generated artifacts.
- Explicit
cgc watch service startup was used despite ENABLE_AUTO_WATCH=false.
Observed concurrency exception (earlier run, Oct 31 2026, ~10:22 EET)
Multiple debounce timer threads failed with the same exception. Relevant traceback, shortened to remove user-specific absolute paths:
threading.py ... self.function(*self.args, **self.kwargs)
codegraphcontext/core/watcher.py:241
self._debounce(event.src_path, lambda: self._handle_modification(event.src_path))
codegraphcontext/core/watcher.py:232, in _handle_modification
self.graph_builder.link_function_calls(files_to_link, self.imports_map)
codegraphcontext/tools/graph_builder.py:793, in link_function_calls
groups = build_function_call_groups(...)
codegraphcontext/tools/indexing/resolution/calls.py:2158, in build_function_call_groups
effective_imports_map = _get_lang_imports(caller_lang) if caller_lang else imports_map
codegraphcontext/tools/indexing/resolution/calls.py:2071, in _get_lang_imports
for name, paths in imports_map.items():
RuntimeError: dictionary changed size during iteration
This suggests that multiple watcher callbacks may be concurrently reading/mutating the same import dictionary. The exact mutation site / lock discipline has not yet been verified.
Severe memory / sync behavior (later run, Oct 31 2026)
The user service started at 14:06 EET:
14:06:13 Watching ... for changes...
14:06:13 Already indexed. Synchronizing current files...
The service was manually stopped at 17:46 EET without a subsequent Monitoring for file changes... message.
At inspection:
- Service memory: 30.2 GiB RAM, 30.4 GiB swap
- Peak memory: 44.2 GiB RAM, peak swap 31.3 GiB
- Main
cgc watch Python process RSS: approximately 30 GiB
- Service: 177 tasks, with approximately 3h 39m total CPU time
- Host before shutdown: 62 GiB total RAM, 56 GiB used, 34 GiB swap used, ~6.1 GiB available
- After stopping
cgc-watch-szu.service: RAM used fell to 24 GiB, swap used to 4.1 GiB, ~37 GiB available.
On an earlier watcher boot, initial synchronization took nearly two hours before logging Monitoring for file changes....
Expected
- Concurrent file change notifications should not mutate an import dictionary while another callback iterates it.
- A watcher should serialize/batch per-repository work or otherwise synchronize shared import state to avoid
RuntimeError.
- Re-index/synchronization memory should remain bounded and release temporary state after processing.
- Failure in an individual change handler should be contained/recovered without persistent resource growth.
Reproduction outline
- Configure CGC 0.5.1 with FalkorDB,
PARALLEL_WORKERS=4.
- Start
cgc watch on a large mixed-language repository with many files being edited (including automated refactoring).
- Observe file-change processing and repeated debounce callbacks in
journalctl.
- Watch for
RuntimeError: dictionary changed size during iteration in calls.py and monitor process RSS / swap / synchronization duration.
Reproduction is not yet deterministic, and the exception and RAM exhaustion occurred in different runs. No minimal reproducer or heap profile is available.
Related issues
Thanks for investigating. A useful starting point may be the shared imports_map accessed from debounce callbacks, and whether in-flight relink work holds onto old maps / large intermediate graph structures.