Skip to content

fix(server): stop client-less session servers from burning a core - #18

Open
m-szymanska wants to merge 13 commits into
developfrom
fix/idle-server-cpu
Open

fix(server): stop client-less session servers from burning a core#18
m-szymanska wants to merge 13 commits into
developfrom
fix/idle-server-cpu

Conversation

@m-szymanska

Copy link
Copy Markdown
Member

Problem

A vc-frame session server with no client attached burns a steady 30–50% of a
core, indefinitely. Triage buckets (Failed runs, Finalized runs, Live runs)
are created automatically after failed runs, never get a client, and never exit —
so the cost accumulates with every bucket the machine collects.

Measured on two fleet machines independently (different users, different session
sets). Sum on one macbook: 1.3–1.8 cores burned continuously; on another host,
four client-less servers summed to ~97%.

Measurement method matters here. ps %CPU and Activity Monitor report a
lifetime average, which hides this for long-lived processes. All numbers below
are delta CPU-time over a known window (ps -o time before/after ÷ window).

The signature that rules out "normal work": delta in the window matches the
lifetime average almost 1:1. That means a constant burn rate from process start,
uncorrelated with any workload — the fingerprint of a fixed-frequency loop, not
of doing something.

Two independent defects

1. Socket theft — zellij-server/src/lib.rs:810

Startup did remove_file + bind on the session socket without probing whether
the current owner is alive. Trigger: SESSION_PROBE_TIMEOUT of 250 ms — a live
but busy server fails to answer ConnStatus in time and is declared dead. Its
socket gets unlinked and rebound by the newcomer, and now two servers back one
session name. On one machine zellij.log recorded 14 server starts against 1
registered exit in a single day.

Fix: new probe_socket_ownership() in zellij-utils/src/sessions.rs. A
successful connect() means the owner is alive, regardless of whether it gets
around to replying — which is precisely what kills the 250 ms trigger. Only
Vacant (file missing, ConnectionRefused, or not-a-socket) permits
remove_file + bind; Live and Unknown log and exit cleanly.

This needs a companion guard on FirstClientConnected: a client of the losing
server is routed to the winner, and without the guard it would call
init_session over live state and wipe an existing session.

2. The 1 Hz loop between session-manager and host

Root cause of the latch: both sets of status-bar plugin targets are derived from
active_tab_ids. When the last client detaches that set is empty, so nobody
receives vc.status-bar-visibility.v1 = "false" — the rail keeps its timer
running forever, on a server no one is looking at.

The cost is O(N²): N servers each re-parse metadata for N sessions, every second,
on wasmi (an interpreter, not a JIT). Per-thread measurement confirms it: ~94%
of the process CPU sits on a single thread, plugin-exec-1, where
pinned_executor holds session-manager and vc-tab-title.

Fix: status_bar_plugin_target_transition() now sends hide to the targets
that were previously visible to the departing client. The loop dies within ~1 s
of detach; attach resumes through the existing path (AddClient → subscribe →
refresh).

Third commit is a smaller, related win: get_session_list no longer re-broadcasts
UpdateSessionInfos to every plugin. A plugin-initiated read shouldn't fan out a
write; freshness is already maintained by the existing metadata job.

Result (measured on the built binary)

Same machine, same moment, 60 s settle then a 30 s window — the only variable is
the binary:

server binary clients delta CPU (30 s) burn
fresh session this branch 0 0.27 s 0.9%
Failed runs bucket this branch 0 0.46 s 1.5%
Finalized runs bucket this branch 0 0.51 s 1.7%
control develop 1 8.58 s 28.6%

Roughly a 20–30× reduction on client-less servers.

Gates

cargo check -p zellij-utils -p zellij-server   → Finished in 28.58s
cargo test  -p zellij-server --lib             → ok. 1309 passed; 0 failed
cargo test  -p zellij-utils  --lib             → 516 passed; 6 failed (pre-existing)

The 6 failures in zellij-utils are the default-KDL-config snapshots; verified
by stash that they fail identically on clean f3e2570ed. New tests cover
"0 clients → chrome parked", "busy-but-alive socket = Live", and
"stale socket = Vacant". rustfmt clean, clippy reports nothing new.

Docs in the same cut: docs/VC_FRAME_OPERATOR_SURFACE.md (new "Session Socket
Ownership" section plus the detach contract) and three CHANGELOG.md entries.

Known limits

  • TOCTOU between probe and bind is reduced by an order of magnitude — the
    dominant trigger is gone — but not closed to zero. Full closure needs a
    per-session advisory lock.
  • Deliberately out of scope, as separate and smaller topics: caching the KDL scan
    in the host (that cost shows up with a client attached), a content-hash gate on
    SessionUpdate, wasmi → JIT, and duplicate detection in vc-frame doctor.

Note for reviewers

f3e2570ed in the commit list is an empty merge commit inherited from the branch
point — its tree is identical to origin/develop, so the diff of this PR is
exactly the three commits above.

m-szymanska and others added 4 commits August 11, 2026 15:07
A starting server unlinked the session socket file unconditionally and
bound its own. The previous server kept running - unreachable, with zero
clients, and with no reaper that ever collects it. Session discovery made
this routine: a server too busy to answer a ConnStatus probe within 250ms
was reported as nonexistent, so a new server was spawned and stole the
name.

Probe the path for ownership before touching it: on Unix a successful
connect means some process holds the listening end, whatever its health;
on Windows the marker PID answers. Only a missing, stale or non-socket
path is cleaned up and re-bound. Anything else - including an
unclassifiable transport error - refuses the start with a log line.

Since the losing server now exits instead of stealing, its client can
reach the surviving server and ask it for a new session. Reject that too:
re-initializing would replace a live session's state wholesale.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Both chrome target sets are derived from active_tab_ids, so the last
detach empties them and nobody is told it stopped being visible. The
plugin instances outlive their client: a session rail latched visible
kept re-arming its 1Hz timer and re-reading every live session's KDL
metadata on a server nobody was watching - the dominant idle CPU cost,
quadratic in the number of live sessions on the machine.

Remember which targets were told they are visible and park those whose
client is no longer connected. Targets of still-connected clients stay
governed by all-minus-active, so tab switching and projector bindings
are untouched. Attach re-activates through the existing active-target
path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A plugin-initiated read sent its result to Screen, which broadcast
SessionUpdate to every plugin - including the caller, which rebuilt its
model and asked again on its next timer. One visible rail therefore woke
every plugin in the session once a second, and the payload is the sum of
all live sessions' metadata on the machine.

Screen's cache is owned by the session-metadata background job, which
already refreshes it on its own cadence, so the read stays a read.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@claude claude 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.

⚠️ Code review skipped — your organization has reached its monthly code review spending cap.

An organization admin can view or raise the cap at claude.ai/admin-settings/claude-code. The cap resets at the start of the next billing period.

Once the cap resets or is raised, reopen this pull request to trigger a review.

@github-actions

Copy link
Copy Markdown
Contributor

🤖 Hi @m-szymanska, I've received your request, and I'm working on it now! You can track my progress in the logs for more details.

@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 Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 13394b1d16

ℹ️ 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread zellij-utils/src/sessions.rs Outdated
Comment on lines +318 to +319
if alive {
SocketOwnership::Live

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Verify named-pipe ownership instead of trusting a reused PID

On Windows, if a server crashes and leaves its marker behind, its PID can later be reused by an unrelated process. OpenProcess then classifies the vacant session as Live, so the startup path exits before attempting the named-pipe bind and that session name remains unusable until the marker is manually removed or the unrelated process exits. Probe the named pipe itself or validate that the PID belongs to the expected server before refusing startup.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Agreed, and taken further in c5c7779: the non-Unix probe is removed rather than reworked. Probing the named pipe by connecting would occupy the target server's accept loop (every accepted stream is paired with a blocking accept on the reply pipe), and no probe is needed there in the first place — the OS refuses to hand out the pipe name twice and ipc_bind writes the marker only after the bind succeeds, so the bind already decides ownership. Docs now state that the probe is Unix-only.

_ => {},
}
match ipc_connect_timeout(path, SOCKET_OWNERSHIP_PROBE_TIMEOUT) {
Ok(_stream) => SocketOwnership::Live,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Avoid dropping the ownership probe during server initialization

When two processes create the same session concurrently, the winner can have bound its socket while session_data is still None; this successful probe then connects and immediately drops the stream. The listener registers that stream as a client, and route_thread_main sends ServerInstruction::RemoveClient on EOF, whose regular-client branch unconditionally unwraps session_data.as_ref(). That panics the winning server during its startup window while the probing server exits because it observed Live, potentially leaving both clients failed or stuck retrying a stale socket. The probe connection or pre-initialization removal path needs to avoid this cleanup panic.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member 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 99fd26a. The unwrap on session_data in the regular-client RemoveClient branch is now a read() + map(), so a connection that ends before this server initialized a session — the racing probe, or any client that gives up in the startup window — no longer panics the server that won the socket. Screen and the plugin thread are notified only when a session actually exists.

Comment thread zellij-server/src/lib.rs Outdated
),
},
);
remove_client!(client_id, os_input, session_state, session_data);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep the rejected client ID reserved until its route exits

When a losing create-session client reaches an already initialized server, this removes its SessionState entry immediately, but its inbound route thread remains alive and later sends ServerInstruction::RemoveClient(client_id) when it receives ClientExited or EOF. Because SessionState::new_client always reuses the lowest vacant ID, a new attach accepted during that window can receive the rejected client's ID; the stale route cleanup then removes and disconnects that unrelated new client. Defer freeing the ID until the route terminates, or associate cleanup with a connection generation so it cannot act on a reused ID.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member 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 99fd26a. The rejection path no longer frees the client id; the route thread is still alive and sends its own RemoveClient when the connection ends, so the id stays reserved until then and can no longer be handed to an attach accepted in that window.

vetcoders-agents added 2 commits August 11, 2026 21:38
Two cleanup paths acted on state that need not exist yet.

RemoveClient unconditionally unwrapped `session_data` to notify Screen and
the plugin thread. A connection can end before this server ever initialized
a session — the socket-ownership probe of a racing server connects and drops
immediately, and so does any client that gives up inside the startup window.
The unwrap then panicked the server that had just won the socket, leaving
both racers with nothing. Notify only when a session is actually there.

The rejection path for a losing create-session client freed its client id
right away, while its route thread stayed alive and would send its own
RemoveClient later. Since `SessionState::new_client` always reuses the lowest
vacant id, an attach accepted in that window could receive the rejected id
and be disconnected by the late cleanup. Leave the id reserved until the
route terminates and sends the removal itself.
The Windows implementation answered ownership from the PID written in the
session marker file. A PID recycled after a crash reads as live, which
strands the session name until someone removes the marker by hand — strictly
worse than not probing at all.

No probe is needed there. Off Unix the listener is a named pipe whose name
the OS refuses to hand out twice, and `ipc_bind` writes the marker only after
that bind succeeds, so the bind itself already decides ownership. Probing by
connecting to the pipe would be worse still: it occupies the target server's
accept loop, which pairs every accepted stream with a blocking accept on the
reply pipe.

Document the probe as the Unix-only mechanism it is.

@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 Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c5c7779f91

ℹ️ 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread zellij-server/src/lib.rs
// running but unreachable: an orphan with zero clients that no
// reaper ever collects, burning CPU until reboot. A stale file
// left by a crashed server stays legal to clean up.
match zellij_utils::sessions::probe_socket_ownership(&socket_path) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Gate the Unix-only ownership probe at its call site

When building zellij-server for Windows, this unconditional call cannot resolve because probe_socket_ownership is now declared only under #[cfg(unix)]. As a result, the Windows server fails to compile before it can use the intended named-pipe bind path; wrap the probe-and-unlink block in #[cfg(unix)] and let non-Unix platforms proceed directly to ipc_bind.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member 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 622227c. The call site and the SocketOwnership import are both #[cfg(unix)] now, so the non-Unix server goes straight to ipc_bind, matching where the probe is actually defined.

The comment left in place says why no non-Unix probe is owed rather than that one is missing: off Unix the session path is a marker file and the listener is a named pipe whose name the OS refuses to hand out twice, so ipc_bind — which writes the marker only after that bind succeeds — already decides ownership.

Verified: Build (Windows) passes on 622227c (run 31530847119), where it had failed on the previous commit. Local gates on the same tree: cargo check -p zellij-server, cargo fmt --check, cargo test -p zellij-utils --lib sessions (9 passed) and cargo test -p zellij-server --lib (1309 passed).

`probe_socket_ownership` only exists under `#[cfg(unix)]`, but the server
startup path called it unconditionally, so the Windows build stopped
resolving it. Gate the call and its import the same way and say in place
why the non-Unix path needs no probe: there the session path is a marker
file and the listener is a named pipe whose name the OS refuses to hand
out twice, so `ipc_bind` already decides ownership.
…ons on a focused rail

- LOCK (^g) routes raw keys to the focused pane; a focused rail consuming
  bare Up/Down became a hidden, mode-proof session switcher
- product key-contract v3: arrow session switching lives only in the ^T
  tab-mode binds (vc_rail_nav pipe) and the always-on Super chords
- rail keeps Enter/ordinals/bucket hotkeys/+/-/Esc; mouse hover+click untouched
- new contract test rail_ignores_bare_arrow_keys (RED observed pre-cut);
  session-manager suite 120/120, fmt + clippy -D warnings clean

Authored-By: claude <agents@vetcoders.io>
Fix held-command reruns by reserving active PTY identifiers before respawn, harden E2E readiness around exact accepted frames, and isolate command fixtures. Extend triage startup budgets for real fresh-session latency, normalize wire-contract line endings, reject symlink traversal during legacy migration, refresh deterministic snapshots and plugin assets, and inventory the clinic current-exe diagnostic in the Semgrep evidence surface.

Authored-By: codex <agents@vetcoders.io>

session_id: 019ff225-49ff-72b0-9078-139cdc37ff61

time: 2026-08-11T23:06:24:z

runtime: headless
Update the canonical plugin SHA256 receipt for the deterministic session-manager WASM produced by the focused-rail change. This restores the asset-integrity contract on Windows and no-default-features CI jobs.

Authored-By: codex <agents@vetcoders.io>

session_id: 019ff225-49ff-72b0-9078-139cdc37ff61

time: 2026-08-11T23:18:06:z

runtime: headless
Serialize the three intentional-panic pinned-executor tests. Windows backtrace symbolization writes through the global panic hook slowly enough that concurrent probes consumed one another’s five-second receipt deadlines even though the executor caught the panics and remained healthy.

Authored-By: codex <agents@vetcoders.io>

session_id: 019ff225-49ff-72b0-9078-139cdc37ff61

time: 2026-08-11T23:29:26:z

runtime: headless
Make mirrored-session snapshot readiness prove that the Vibecrafted brand is rendered in the first terminal row. The prior anywhere-in-frame predicate could match guide content while the top bar was still a transient blank frame.

Authored-By: codex <agents@vetcoders.io>

session_id: 019ff225-49ff-72b0-9078-139cdc37ff61

time: 2026-08-11T23:57:52:z

runtime: headless
Add an explicit foreground-server runtime contract for isolated supervisors and enable it in the triage runtime fixture. This prevents Unix double-fork reparenting from breaking the harness process-ownership proof while preserving normal daemon behavior outside the fixture.

Authored-By: codex <agents@vetcoders.io>
session_id: 019ff225-49ff-72b0-9078-139cdc37ff61
time: 2026-08-12T00:09:45+02:00
runtime: headless
Copilot AI lite review requested due to automatic review settings August 11, 2026 22:51

Copilot AI 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.

Pull request overview

This PR addresses sustained high CPU usage in vc-frame session servers that have no attached clients by preventing socket “theft” between competing servers, ensuring chrome plugins are explicitly “parked” on detach, and eliminating a plugin-driven feedback loop that caused quadratic work.

Changes:

  • Add a Unix-only socket ownership probe to prevent unlink+rebind when a live server is still listening; add a guard to prevent re-initializing an already-running session.
  • Fix chrome visibility targeting so the last client detach triggers explicit hide/park messages and stops background refresh loops; stop get_session_list from fanning out UpdateSessionInfos.
  • Improve E2E and runtime harness robustness around foreground server ownership and snapshot readiness, plus related fixture/snapshot updates.

Reviewed changes

Copilot reviewed 77 out of 79 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap Snapshot updates for default config/keybind rendering
zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap Snapshot updates for config/keybind rendering
zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap Snapshot updates for config/keybind rendering
zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap Snapshot updates for config/keybind rendering
zellij-utils/src/sessions.rs Add SocketOwnership + ownership probe and tests
zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string.snap Snapshot updates for KDL serialization
zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string_with_comments.snap Snapshot updates for KDL serialization (with comments)
zellij-utils/src/envs.rs Add VC_FRAME_SERVER_FOREGROUND env flag helper
zellij-utils/src/consts.rs Harden legacy-path migration behavior and add symlink test
zellij-utils/src/client_server_contract/mod.rs Normalize line endings for contract hashing + tests
zellij-utils/assets/plugins/SHA256SUMS Update plugin artifact checksum(s)
zellij-server/src/unit/screen_tests.rs Add unit test covering “last client detach parks chrome”
zellij-server/src/screen.rs Track last visible chrome targets to park on detach
zellij-server/src/plugins/zellij_exports.rs Remove UpdateSessionInfos side-effect from get_session_list
zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__override_layout_plugin_command.snap Snapshot update for plugin layout override
zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__dump_layout_success_plugin_command.snap Snapshot update for layout dump output
zellij-server/src/plugins/pinned_executor.rs Serialize panic-related tests to reduce Windows flakiness
zellij-server/src/os_input_output.rs Reserve terminal IDs for rerun and resolve reserved-spawn failures safely
zellij-server/src/os_input_output_windows.rs Implement terminal-id reservation for rerun on Windows
zellij-server/src/os_input_output_unix.rs Implement terminal-id reservation for rerun on Unix + tests
zellij-server/src/lib.rs Add socket-ownership startup guard, foreground option, and session init guard
zellij-client/src/lib.rs Support foreground server mode by detaching child process handling
tools/triage_runtime_e2e_test.py Add tests for isolated foreground server ownership proof
tools/semgrep_inventory.py Extend current-exe policy inventory for clinic path
src/tests/fixtures/append-echo-script.sh Make CLI-run fixture deterministic across reruns
src/tests/e2e/snapshots/vc_frame__tests__e2e__cases__watcher_client_functionality.snap E2E snapshot updates for new chrome/layout
src/tests/e2e/snapshots/vc_frame__tests__e2e__cases__watcher_client_functionality-2.snap E2E snapshot updates for new chrome/layout
src/tests/e2e/snapshots/vc_frame__tests__e2e__cases__use_custom_layout_with_relative_path.snap E2E snapshot updates
src/tests/e2e/snapshots/vc_frame__tests__e2e__cases__undo_rename_tab.snap E2E snapshot updates
src/tests/e2e/snapshots/vc_frame__tests__e2e__cases__undo_rename_pane.snap E2E snapshot updates
src/tests/e2e/snapshots/vc_frame__tests__e2e__cases__typing_exit_closes_pane.snap E2E snapshot updates
src/tests/e2e/snapshots/vc_frame__tests__e2e__cases__toggle_pane_fullscreen.snap E2E snapshot updates
src/tests/e2e/snapshots/vc_frame__tests__e2e__cases__toggle_floating_panes.snap E2E snapshot updates
src/tests/e2e/snapshots/vc_frame__tests__e2e__cases__tmux_mode.snap E2E snapshot updates
src/tests/e2e/snapshots/vc_frame__tests__e2e__cases__status_bar_loads_custom_keybindings.snap E2E snapshot updates
src/tests/e2e/snapshots/vc_frame__tests__e2e__cases__starts_with_one_terminal.snap E2E snapshot updates
src/tests/e2e/snapshots/vc_frame__tests__e2e__cases__start_without_pane_frames.snap E2E snapshot updates
src/tests/e2e/snapshots/vc_frame__tests__e2e__cases__split_terminals_vertically.snap E2E snapshot updates
src/tests/e2e/snapshots/vc_frame__tests__e2e__cases__send_command_through_the_cli.snap E2E snapshot updates for rerun output
src/tests/e2e/snapshots/vc_frame__tests__e2e__cases__send_blocking_command_through_the_cli.snap E2E snapshot updates
src/tests/e2e/snapshots/vc_frame__tests__e2e__cases__scrolling_inside_a_pane.snap E2E snapshot updates
src/tests/e2e/snapshots/vc_frame__tests__e2e__cases__scrolling_inside_a_pane_with_mouse.snap E2E snapshot updates
src/tests/e2e/snapshots/vc_frame__tests__e2e__cases__resize_terminal_window.snap E2E snapshot updates
src/tests/e2e/snapshots/vc_frame__tests__e2e__cases__resize_pane.snap E2E snapshot updates
src/tests/e2e/snapshots/vc_frame__tests__e2e__cases__quit_and_resurrect_session.snap E2E snapshot updates
src/tests/e2e/snapshots/vc_frame__tests__e2e__cases__quit_and_resurrect_session_with_viewport_serialization.snap E2E snapshot updates
src/tests/e2e/snapshots/vc_frame__tests__e2e__cases__pin_floating_panes.snap E2E snapshot updates
src/tests/e2e/snapshots/vc_frame__tests__e2e__cases__override_layout_from_default_to_compact.snap E2E snapshot updates
src/tests/e2e/snapshots/vc_frame__tests__e2e__cases__open_new_tab.snap E2E snapshot updates
src/tests/e2e/snapshots/vc_frame__tests__e2e__cases__multiple_users_in_same_pane_and_tab.snap E2E snapshot updates
src/tests/e2e/snapshots/vc_frame__tests__e2e__cases__multiple_users_in_same_pane_and_tab-2.snap E2E snapshot updates
src/tests/e2e/snapshots/vc_frame__tests__e2e__cases__multiple_users_in_different_tabs.snap E2E snapshot updates
src/tests/e2e/snapshots/vc_frame__tests__e2e__cases__multiple_users_in_different_tabs-2.snap E2E snapshot updates
src/tests/e2e/snapshots/vc_frame__tests__e2e__cases__multiple_users_in_different_panes_and_same_tab.snap E2E snapshot updates
src/tests/e2e/snapshots/vc_frame__tests__e2e__cases__multiple_users_in_different_panes_and_same_tab-2.snap E2E snapshot updates
src/tests/e2e/snapshots/vc_frame__tests__e2e__cases__move_tab_to_right.snap E2E snapshot updates
src/tests/e2e/snapshots/vc_frame__tests__e2e__cases__move_tab_to_right_until_it_wraps_around.snap E2E snapshot updates
src/tests/e2e/snapshots/vc_frame__tests__e2e__cases__move_tab_to_left.snap E2E snapshot updates
src/tests/e2e/snapshots/vc_frame__tests__e2e__cases__move_tab_to_left_until_it_wraps_around.snap E2E snapshot updates
src/tests/e2e/snapshots/vc_frame__tests__e2e__cases__mirrored_sessions.snap E2E snapshot updates
src/tests/e2e/snapshots/vc_frame__tests__e2e__cases__mirrored_sessions-2.snap E2E snapshot updates
src/tests/e2e/snapshots/vc_frame__tests__e2e__cases__lock_mode.snap E2E snapshot updates
src/tests/e2e/snapshots/vc_frame__tests__e2e__cases__load_plugins_in_background_on_startup.snap E2E snapshot updates
src/tests/e2e/snapshots/vc_frame__tests__e2e__cases__focus_pane_with_mouse.snap E2E snapshot updates
src/tests/e2e/snapshots/vc_frame__tests__e2e__cases__detach_and_attach_session.snap E2E snapshot updates
src/tests/e2e/snapshots/vc_frame__tests__e2e__cases__close_pane.snap E2E snapshot updates
src/tests/e2e/snapshots/vc_frame__tests__e2e__cases__cannot_split_terminals_vertically_when_active_terminal_is_too_small.snap E2E snapshot updates
src/tests/e2e/snapshots/vc_frame__tests__e2e__cases__bracketed_paste.snap E2E snapshot updates
src/tests/e2e/remote_runner.rs Improve readiness predicate stability and add chrome detection helpers
src/tests/e2e/cases.rs Make E2E snapshot normalization more robust; tighten readiness waits/retries
src/run_triage_cli.rs Increase timeouts for session readiness and inventories
security/semgrep/EVIDENCE.md Update semgrep evidence text for clinic current_exe usage
security/semgrep/baseline.json Update semgrep baseline metadata counts/hashes
scripts/triage-runtime-e2e.py Enforce foreground server ownership in isolation and improve cleanup proofs
docs/VC_FRAME_OPERATOR_SURFACE.md Document detach parking and session socket ownership behavior
default-plugins/session-manager/src/main.rs Stop consuming bare arrow keys in focused rail; add regression test
CHANGELOG.md Add unreleased entries for the fixes and perf improvement

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +199 to 206
let source_metadata = match std::fs::symlink_metadata(source) {
Ok(metadata) => metadata,
Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(()),
Err(error) => return Err(error),
};
if target.exists() {
return Ok(());
}
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.

3 participants