Skip to content

fix(hooks): SessionStart prunes orphaned code graphs even when it ingests nothing (#711, slice 2) - #897

Merged
cdeust merged 3 commits into
mainfrom
fix/711-session-start-graph-prune
Nov 6, 2026
Merged

cdeust merged 3 commits into
mainfrom
fix/711-session-start-graph-prune

Conversation

@cdeust

@cdeust cdeust commented Nov 5, 2026 •

Copy link
Copy Markdown
Owner

Summary

Slice 2 of #711. Slice 1 (#869) ties each graph under ~/.cache/cortex/code-graphs to its project root (cortex-root.json) and removes graphs whose root is gone, but it runs that pruning only after ingest_codebase has built a graph. A session that ingests nothing (fresh graph, no pipeline installed, failed analyze) leaves every deleted worktree's graph (0.6 to 1.1 GB each, measured 2026-10-10 in the issue) on disk.

Root cause: the only caller of prune_orphan_graphs is the tail of a successful ingest (ingest_codebase._prune_precedent_graphs).

Change:

  • code_graph_cache.orphan_graphs(cache_dir) lists the orphans by reading the markers, without deleting anything; prune_orphan_graphs now uses the same classification.
  • The SessionStart hook (_maybe_background_prune_graphs) calls it on the managed cache. When at least one orphan exists, it starts the new detached mcp_server.hooks.code_graph_prune_background worker (log: ~/.claude/methodology/code_graph_prune.log), so rmtree never runs inside the hook's time budget. Failures are logged, never raised.
  • A directory with no marker is still reported and kept (slice 1 rule unchanged).
  • Security (Codex review on b5a2a45, P1): without a launcher, child_command ran [python, "-m", module] from the hook's cwd, which is the user's project, so a planted mcp_server/hooks/<module>.py could replace the worker. child_command now passes -P (Python 3.11+), which keeps the cwd out of sys.path, for every child it builds: consolidate, capture, session lifecycle and the new prune worker. test_launcher_command proves it.
  • The tests that run the whole SessionStart hook stub the new call, so they never prune a developer's real cache.

Refs #711 (does not close it: remaining slices are a size cap with eviction, pruning when a worktree is removed, and a worktree reusing its parent's graph).

Type of change

  • Bug fix (non-breaking change which fixes an issue)

Test plan

  • New tests_py/hooks/test_session_start_graph_prune.py (5 tests): the scan names only the orphan and deletes nothing (live and unmarked graphs are excluded); one orphan starts exactly one worker with the right module and log; no orphan starts nothing; a failed spawn is logged, not raised; the worker removes the orphan, keeps the live graph and exits 0.
  • pytest tests_py/hooks tests_py/infrastructure/test_code_graph_cache.py tests_py/handlers tests_py/test_ingest_progress.py: 2727 passed, 113 skipped, 2 failed. The 2 failures (test_entry_auto_dispatch.py::test_engine_selected_auto_backend_reaches_hook[False-*]) fail the same way on origin/main (6a49d69) in this environment.
  • pytest tests_py/scripts: 1851 passed after the worker got its wire_composition_root() call (caught by test_composition_root_wiring_coverage.py).
  • python scripts/check_craftsmanship.py: OK. ruff check and ruff format --check (0.17.3, from uv.lock) on the changed files: clean.
  • Manual: HOME=$(mktemp -d) python -m mcp_server.hooks.code_graph_prune_background printed removed=0 unattributed=0 failed=0, exit 0.

Audit notes

  • Not verified: the hook's added latency on a real cache with ~60 entries (one small JSON read per entry, not measured here), and the mutation gate, which needs CI.
  • Torch was not installed locally (its download host is blocked in this session); none of the tests touched need it.

Coding-standards compliance

  • §2.2 Layer direction preserved: hooks → infrastructure only.
  • §4.1 / §4.2 File and function limits (craftsmanship gate OK).
  • §8 No new numeric constant.
  • §9 No dead code.

Generated by Claude Code

cdeust commented Nov 5, 2026

Copy link
Copy Markdown
Owner Author

CI autopilot: unit failed on the mutation gate (ADR-1122). This PR caused it. Fix pushed in b5a2a45.

Run 42122711761, on 203e5d5:

  • The suite passed: 11742 passed, 26 skipped.
  • 21 mutants survived in the changed functions:
    • code_graph_prune_background.main: 6 survivors;
    • _session_start: 13 survivors, all on arguments passed to collaborators that the existing tests stub without checking what they receive;
    • _maybe_background_prune_graphs: 2 survivors, the message logged after a spawn.

Diagnosis:

  • The worker printed only counts.
  • Its logging.basicConfig(...) does nothing under pytest, which has already set up logging, so no test could observe a mutation of it.
  • No test checked the arguments passed to the stubbed collaborators.

Fix:

  • The worker now prints the removed, kept and failed names, with no basicConfig.
  • A failed removal is tested (exit 1).
  • The spawn test checks the detach options and the log line.
  • test_session_start_dispatch.py checks every argument _session_start passes to its collaborators, on the SQLite, PostgreSQL banner, empty-store and failed-connection paths.
  • Locally, scripts/ci_mutation_diff.py reports 4 file(s), 0 failed.

Not verified: locally, without PostgreSQL, _session_start gave 9 survivors against 13 in CI. The 4 that only appear in CI should be covered by the PostgreSQL-path tests (which mock the connection), but only CI on b5a2a45 will confirm it.


Generated by Claude Code

@cdeust
cdeust marked this pull request as ready for review November 6, 2026 14:19
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-06T15:09:23.168627Z f678611 New commits
🔒 Security Review ✅ Completed 2026-11-06T15:12:45.369891Z f678611 New commits

Security findings

Advisory findings (2)

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@claude

claude Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

Claude finished @cdeust's task in 2m 40s —— View job


Review 1 of 3 on b5a2a45: no Blockers, approved

  • Gather the diff and context
  • Run five foreground reviews (quality, security, performance, tests, docs)
  • Check findings against the code
  • Post the summary and verdict: review comment

All three Notes are recorded in the review comment; none needs action before merge:

  • Worker races: several prune workers can run at once and report false failures.
  • Unreachable roots: the graph of a project on an unmounted drive or network share is now deleted at the next SessionStart.
  • Docs: the log-rotation runbook and the hermetic test helper do not mention the new log and worker.

I did not run the tests myself. The test review read the code without running pytest.

@claude

claude Bot commented Nov 6, 2026

Copy link
Copy Markdown

Review 1 of 3 (ADR-1119): PR #897

Five reviews ran on b5a2a45: code quality, security, performance, tests and docs. None found a Blocker. What they checked:

  • Layers: hooks are allowed to import infrastructure (docs/module-inventory.md:36).
  • What can be deleted: rmtree only ever targets single entries from iterdir() inside the managed cache. The path in the marker only feeds an exists() test, and a symlinked entry makes rmtree raise, which is caught.
  • Spawn: the command is a fixed argv with no shell, and its output reaches the log only after redaction (ADR-1110).
  • Real cache in tests: every test that runs the whole SessionStart hook either stubs the prune or uses a temporary HOME, so none touches a real cache.

Notes, no action before merge

  • session_start_background.py:116-122: no claim slot like claim_reanalysis_slot, so every SessionStart spawns a worker while an orphan stays. When two workers (or the ingest-time prune) remove the same graph, the loser logs FileNotFoundError as a failure and exits 1.
  • code_graph_cache.py:102: a root on an unmounted drive or network share fails Path(root).exists(), so its graph is now deleted at the next SessionStart, not at the next ingest. The rule is unchanged; it just runs far more often.
  • docs/runbooks/local-log-rotation.md:3 and tests_py/_hermetic_hook_env.py:4-7 do not mention the new code_graph_prune.log and the third detached worker.

Verdict: approve

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛡️ Codex Security Review · Automatically triggered

Here are some automated security review suggestions for this pull request.

Reviewed commit: b5a2a45287

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

orphans = code_graph_cache.orphan_graphs(code_graph_cache.managed_cache_dir())
if not orphans:
return
cmd = child_command("mcp_server.hooks.code_graph_prune_background")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛡️ Codex Security Review · Automatically triggered

P1 Badge Security: Isolate the prune worker from project imports

When the native Codex wheel is used and the managed cache contains an orphan, this new spawn runs python -m mcp_server.hooks.code_graph_prune_background without -I or a safe cwd. runtime.py isolates the parent but preserves the session's project directory, and spawn_logged preserves it too. A repository containing mcp_server/hooks/code_graph_prune_background.py therefore executes code at SessionStart as the user's account. This materially adds an immediate trigger to the pre-existing unsafe helper whenever stale worktree graphs exist. Pass -I/PYTHONSAFEPATH or set a trusted cwd. Cortex hooks run with the user's full filesystem permissions.

SECURITY.md reference: SECURITY.md:L5-L20

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed and fixed in the next push (the commit after b5a2a45, fixup! on this PR).

  • Traced: without a launcher (Codex wheel runtime), child_command returned [python, "-m", module] and the spawn inherits the hook's cwd, which is the user's project. -m puts the cwd first on sys.path, so a repository shipping mcp_server/hooks/<module>.py replaced the worker. The same gap existed before this PR for the consolidate, capture and session-lifecycle children.
  • Fix: child_command now passes -P (Python 3.11+, the project's floor), which keeps the cwd out of sys.path, for every child it builds. The launcher path was already safe: there, sys.path[0] is the plugin's scripts/ directory.
  • Proof: test_without_a_launcher_a_module_in_the_cwd_is_not_run plants such a package in the cwd. Without the fix, the planted module ran and the test failed; with the fix, the test passes. The local mutation gate reports 5 files, 0 failed.

Generated by Claude Code

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Second half pushed in d058989. spawn_logged also starts the log relay as python -m mcp_server.infrastructure.log_relay from the same inherited cwd, so a planted relay module had the same effect for every logged spawn, launcher or not. relay_command now passes -P. tests_py/infrastructure/test_log_relay_cwd.py plants the module: it ran before the fix and no longer runs.


Generated by Claude Code

…ests nothing (#711, slice 2)

Slice 1 (#869) pruned graphs whose recorded root is gone, but only after
ingest_codebase had built a graph. A session with a fresh graph, no pipeline
or a failed analyze left every deleted worktree's graph (0.6 to 1.1 GB each,
measured 2026-10-10) on disk.

The SessionStart hook now reads the cortex-root.json markers of the managed
cache (~/.cache/cortex/code-graphs) and, when at least one names a root that
no longer exists, starts the detached code_graph_prune_background worker, so
the removal never runs inside the hook's time budget. A directory without a
marker is still reported and kept.

Refs #711

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018kyZYsdTexaL2aS5FFzdeC
@cdeust
cdeust force-pushed the fix/711-session-start-graph-prune branch from b5a2a45 to a4a577c Compare November 6, 2026 14:36
@claude

claude Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

Claude finished @cdeust's task in 3m 3s —— View job


Review 2 of 3 on a4a577c: one Blocker, blocked

  • Gather the PR commits (the branch was squashed to a4a577c, so b5a2a45 is no longer in its history)
  • Find what changed since b5a2a45: -P in child_command and its test
  • Check the earlier Codex P1: fixed for the worker, but not for the log relay
  • Run the five foreground reviews (quality, security, performance, tests, docs)
  • Post the findings and verdict: review comment

Blocker: the log relay can still be replaced by code from the user's project.

  • spawn_logged starts the new prune worker together with a log relay.
  • The relay runs python -m mcp_server.infrastructure.log_relay without -P (mcp_server/infrastructure/log_relay.py:43).
  • On the Codex wheel runtime the hook's working directory is the user's project, so a repository shipping mcp_server/infrastructure/log_relay.py still runs its own code at SessionStart.
  • Fix: add "-P" to relay_command, plus a test that plants such a package in the working directory.

Not verified: I traced the path by reading the code. I could not reproduce it, because this session could not write files or fetch b5a2a45, and I ran no tests.

@claude

claude Bot commented Nov 6, 2026

Copy link
Copy Markdown

Review 2 of 3 (ADR-1119): PR #897

Scope. The branch was squashed to a4a577c, so b5a2a45 is no longer in its history. Going by the author's reply to the Codex finding, the change since review 1 is -P in child_command (mcp_server/hooks/launcher_command.py) plus test_without_a_launcher_a_module_in_the_cwd_is_not_run. Five reviews ran on that delta: code quality, security, performance, tests and docs.

Earlier findings. Review 1 raised no Blocker. The Codex P1 is fixed for the worker, but not for the second Python process the same spawn starts (see below).

Blocker

mcp_server/infrastructure/log_relay.py:43: the log relay can still be replaced by code from the user's project.

  • The new prune spawn (session_start_background.py:122) calls spawn_logged, which starts two processes: the worker and a log relay.
  • The relay is started first (log_relay.py:75-82) with [sys.executable, "-m", "mcp_server.infrastructure.log_relay", …]. It has no -P and no cwd, and detached_spawn.options() sets no cwd.
  • On the Codex wheel runtime, the hook's working directory is the user's project, because hooks/entry.py does no chdir.
  • With -m, Python puts the current directory at sys.path[0], ahead of the PYTHONPATH that _relay_env adds. So a repository shipping mcp_server/__init__.py and mcp_server/infrastructure/log_relay.py runs its own code at SessionStart whenever an orphan exists. That is the same hole as the P1, and the shadowing code also receives the worker's unredacted output.
  • The launcher path is not affected: scripts/launcher.py:152 changes into the plugin root first.
  • The relay code predates this PR, but this PR adds the trigger, the same reasoning that made the P1 valid.

How it was traced: the code path was read end to end, and the shadowing works by the same -m/sys.path[0] mechanism the new test proves for the child. I could not run a planted-package script here because this session cannot write files.

Fix: add "-P" to relay_command. -P keeps PYTHONPATH, so the relay still resolves through _relay_env. Do not use -I, which drops PYTHONPATH. Then add a planted-package test for the relay like the one for child_command.

Fix this →

Notes, no action before merge

  • tests_py/hooks/test_launcher_command.py:37-54: the test has no positive control (a plain -m run that does create the marker) and inherits PYTHONSAFEPATH/PYTHONPATH from the caller, so either variable can make it pass or fail for an unrelated reason.
  • -P does not drop an empty part of the user's PYTHONPATH (for example ":/x"), which still means the current directory, for the child or the relay. This predates the PR and depends on the user's environment.
  • -P was chosen over -I because -I would also drop PYTHONPATH/PYTHONUTF8. One sentence saying so in the docstring would stop someone from later switching it "for consistency" with runtime.py:76.

Verdict: block

… it ingests nothing (#711, slice 2)

Codex security review on b5a2a45 (P1), second half: spawn_logged also starts the log relay as [python, -m, mcp_server.infrastructure.log_relay] from the inherited cwd, so a planted relay module in the user's project ran for every logged spawn, launcher or not. relay_command now passes -P. test_log_relay_cwd plants the module and shows it no longer runs (it ran before the fix).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018kyZYsdTexaL2aS5FFzdeC
@claude

claude Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

Claude finished @cdeust's task in 1m 19s —— View job


Review 3 of 3 on d058989: no Blockers, approved

  • Check the review 2 Blocker: fixed. relay_command now passes -P (log_relay.py:46), and test_log_relay_cwd.py plants a fake relay module in the working directory to prove it does not run.
  • Run five foreground reviews (quality, security, performance, tests, docs) on d058989 only
  • Post findings and verdict: review comment

What can merge: the whole PR, both commits and all 13 files. No Blocker remains.

Notes, no action before merge:

  • Test environment: if the caller's shell sets PYTHONSAFEPATH, the new test passes even without the fix.
  • Empty PYTHONPATH entry: an entry like ":/x" still puts the working directory ahead of the standard library. This predates the PR.
  • Test cleanup: if a wait times out, the test leaves its two processes running.

I could not run pytest, ruff or the craftsmanship check, because this session's permissions did not allow it. The review comes from reading the code.

@claude

claude Bot commented Nov 6, 2026

Copy link
Copy Markdown

Review 3 of 3 (ADR-1119): PR #897

Scope. I reviewed only d058989, the one commit pushed after a4a577c (the commit review 2 checked). It changes mcp_server/infrastructure/log_relay.py and adds tests_py/infrastructure/test_log_relay_cwd.py. Five reviews ran on it: code quality, security, performance, tests and docs. None found a Blocker.

Review 2 Blocker: fixed. relay_command now passes -P (log_relay.py:46), so the relay no longer puts the user's project directory on sys.path. _relay_env puts the package root first in PYTHONPATH, so the real mcp_server package wins over a planted copy. -P keeps PYTHONPATH, so the module docstring at :15-17 is still true. The interpreter is sys.executable, and the project requires Python 3.11+, which has -P. The new test plants mcp_server/infrastructure/log_relay.py in the spawn's cwd. Without -P, the planted module would run and the real relay would never write the log, so both assertions would fail. Two existing tests check the relay argv, test_worker_log_rotation.py:65 and test_capture_unsupported_platform.py:148. They compare only its tail, so the extra -P does not break them. Ruff: line 46 is 86 characters, under the 88 limit.

Not verified: I ran no tests, ruff or craftsmanship checks, because this session's permissions did not allow pytest. The analysis above comes from reading the code.

Notes, no action before merge

  • test_log_relay_cwd.py:25: the relay inherits PYTHONSAFEPATH from the caller's environment, so a shell that sets it would make the test pass even without -P. Calling monkeypatch.delenv("PYTHONSAFEPATH", raising=False) would close that gap.
  • log_relay.py:43-45: the docstring is accurate for the relay module itself. But an empty entry in the user's PYTHONPATH (for example ":/x") still puts the cwd ahead of the standard library, so a planted queue.py could run. This predates the PR and was already noted in review 2.
  • test_log_relay_cwd.py:28-29: if a wait(timeout=30) times out, the child and relay are left running. A try/finally that kills them would fix this.

What can merge: the whole PR, both commits a4a577c and d058989, all 13 files. No Blocker remains, so nothing needs a refinement PR.

Verdict: approve

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛡️ Codex Security Review · Automatically triggered

Here are some automated security review suggestions for this pull request.

Reviewed commit: d058989a9f

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

"""
return launcher_child_command(module, *arguments) or [
python_executable(),
"-P",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛡️ Codex Security Review · Automatically triggered

P2 Badge Security: Isolate wheel children from inherited PYTHONPATH

When the native Codex wheel hooks are trusted, an orphan graph exists, and inherited PYTHONPATH contains the project (., its path, or an empty component), use -I instead of -P. spawn_logged passes env=None, and -P suppresses only Python's implicit cwd insertion, so the new SessionStart worker imports a project-planted mcp_server.hooks.code_graph_prune_background. The prior default-cwd path is fixed, but a subprocess reproduction with PYTHONPATH=. is fresh evidence that this worker remains replaceable, yielding code execution as the user. Cortex hooks execute automatically with that user's filesystem permissions.

SECURITY.md reference: SECURITY.md:L5-L16

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Traced, and valid. I am not fixing it in this PR: it has used its three reviews (ADR-1119), so the fix goes to a refinement PR, and the owner decides on this thread.

  • Path: the Codex wheel parent runs with -I (plugins/hypermnesia-mcp-codex/scripts/runtime.py:76-79), so it ignores PYTHONPATH. Its children, built by child_command and the log relay, carry only -P and inherit PYTHONPATH (env=None). An entry that names the project (., its path, or an empty component) still lets a planted mcp_server.hooks.<worker> run. The children are therefore less isolated than their parent.
  • Before this PR: consolidate, capture and session-lifecycle already spawned the same way. This PR adds the prune trigger.
  • Proposed refinement:
    1. In the wheel branch of child_command, pass -I, the same isolation as the parent. The child imports only from the venv.
    2. For the relay: keep -P, and stop inheriting the caller's PYTHONPATH, so it is exactly _PACKAGE_ROOT (_relay_env). -I would break the plugin runtime, which finds the package only through that variable.
    3. Tests run each child with PYTHONPATH=. in a cwd that holds a planted module, plus a positive control without the flag.
  • Owner decision needed: whether a child of the plugin launcher should also stop inheriting PYTHONPATH. The launcher changes into the plugin root first, so . no longer names the project there. An absolute path to the project in PYTHONPATH still would.

Generated by Claude Code

… it ingests nothing (#711, slice 2)

Mutation gate (ADR-1122), check unit, run 42236098720: the stats run for log_relay.py failed ("failed to collect stats. runner returned 1"). test_log_relay_cwd spawned the real relay with the inherited MUTANT_UNDER_TEST, so mutmut's trampoline ran in the planted project cwd, found no [tool.mutmut] config and died, and out.log was never written. The relay subprocess now drops MUTANT_UNDER_TEST, as tests_py/_hermetic_hook_env.py does; relay_command still runs in the test process, so its mutants are judged. PYTHONSAFEPATH is dropped too, so the test fails without -P (review 3 note).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018kyZYsdTexaL2aS5FFzdeC

cdeust commented Nov 6, 2026

Copy link
Copy Markdown
Owner Author

CI autopilot: on d058989, unit (and merge, which depends on it) failed in the mutation gate's stats run for log_relay.py. This PR caused it. Fix pushed in f678611.

Run 42236098720: the suite passed (11872 passed, 26 skipped). Then log_relay.py: failed to collect stats. runner returned 1.

Diagnosis, reproduced locally:

  • test_log_relay_cwd started the real relay, and the relay inherited MUTANT_UNDER_TEST.
  • mutmut's trampoline then ran in the test's project cwd. It found no [tool.mutmut] configuration there and died with Could not figure out where the code to mutate is.
  • So out.log was never written and the test failed.

Fix: the relay subprocess drops MUTANT_UNDER_TEST, as tests_py/_hermetic_hook_env.py:97-102 already does. Its mutants are still judged, because relay_command runs inside the test process. PYTHONSAFEPATH is dropped too, so the test fails without -P (a note from review 3).

Local receipt: scripts/ci_mutation_diff.py reports 6 file(s), 0 failed, 0 untested. CI on f678611 has not reported yet.


Generated by Claude Code

@cdeust
cdeust merged commit d3da98f into main Nov 6, 2026
18 checks passed
@cdeust
cdeust deleted the fix/711-session-start-graph-prune branch November 6, 2026 15:39
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.

2 participants