oxideterm-node-session-ownership
GitHub定义SSH节点会话生命周期管理规则,确保UI与后端解耦。规范节点创建、共享、断开及后台任务的所有权归属,防止资源泄漏和状态不一致。
Trigger Scenarios
Install
npx skills add AnalyseDeCircuit/oxideterm --skill oxideterm-node-session-ownership -g -y
SKILL.md
Frontmatter
{
"name": "oxideterm-node-session-ownership",
"description": "Apply OxideTerm's runtime ownership rules when implementing or reviewing SSH nodes, terminal panes, SFTP, port forwarding, reconnect, jump hosts, health checks, NodeRouter, or long-lived backend tasks."
}
OxideTerm Node Session Ownership
Use this skill whenever a change can alter which component creates, shares, closes, reconnects, or observes a remote session or background job.
Core invariant
A connected SSH node is independent from any terminal pane that consumes it. UI lifetime must not silently become backend lifetime.
flowchart TD
W["Workspace or application lifetime"] --> R["NodeRouter and connection registry"]
R --> N["Physical SSH node connection"]
N --> T["Terminal consumer"]
N --> S["SFTP consumer"]
N --> F["Forwarding consumer"]
R --> H["Health and reconnect jobs"]
Mandatory rules
- Node liveness belongs to the connection registry,
NodeRouter, or an explicit node-disconnect operation. - Closing a terminal pane removes that terminal consumer. It must not disconnect a shared node.
- SFTP must acquire a real node-backed SFTP session. A shell command that imitates file transfer is only a documented compatibility fallback.
- Port forwarding owns its listener and bridge tasks. Those tasks must remain alive without an open terminal pane and must stop when the forwarding rule or owning node stops.
- Reconnect, health checks, grace periods, and child-process bridges need an explicit owner and cancellation path tied to node, workspace, or application lifetime.
- Long-lived work must not run on a one-shot runtime that is dropped when a command handler returns.
- A child node connected through a jump host must retain the parent connection for as long as the child transport needs it.
- UI components observe runtime state through router or registry events. They must not infer node liveness from terminal existence.
- Add concise English comments at ownership transfers: creation, sharing, invalidation, reacquisition, cancellation, and deliberate survival after UI teardown.
Forbidden bypasses
- Do not open a second unmanaged SSH transport because a node-backed API is inconvenient.
- Do not find a connection by selecting the first terminal pane associated with a host.
- Do not store a long-lived task only in a temporary dialog, command handler, or terminal view.
- Do not mark a node disconnected merely because one channel, pane, SFTP request, or forwarding consumer closes.
- Do not leave spawned child processes or Tokio tasks detached without a shutdown signal and a bounded completion path.
Recommended practices
- Express each backend dependency as a consumer or owner in the registry instead of relying on incidental
Arcretention. - Keep physical transport state on the connection entry and logical topology in
NodeRouter. - Make disconnect cascades follow recorded parent-child connection identities, not matching host strings.
- Keep task handles or cancellation senders on the object whose lifetime defines the job.
- Prefer event-driven UI updates over polling terminal state.
Compatibility paths
Compatibility fallbacks are allowed only when they are explicit, bounded, and reported accurately. They must not be described as a native SFTP, forwarding, or node-session implementation. Record the owner and cleanup path even for temporary fallbacks.
Examples
Incorrect:
// Closing the pane accidentally destroys the only owner of the shared node.
struct TerminalPane {
ssh_transport: SshTransport,
}
Correct shape:
// The pane owns only its consumer registration; the registry owns transport liveness.
struct TerminalPane {
connection_id: String,
terminal_consumer: ConnectionConsumer,
}
Incorrect:
tokio::runtime::Runtime::new()?.spawn(run_forward_listener());
// The runtime is dropped when the command returns.
Correct shape:
// The forwarding owner retains both cancellation and completion handles.
struct ForwardingJob {
shutdown_tx: Option<oneshot::Sender<()>>,
worker: JoinHandle<()>,
}
Review checklist
- Identify the physical connection owner.
- List every terminal, SFTP, forwarding, reconnect, health, and child-node consumer.
- Verify that closing one consumer does not close unrelated consumers.
- Verify that explicit node disconnect stops dependent jobs and child transports.
- Inspect every spawned task or process for a retained owner, cancellation signal, and cleanup path.
- Check that UI state consumes node events rather than terminal-derived liveness.
- State any remaining compatibility fallback precisely.
Verification
- Close the last terminal and prove the node remains usable by another registered consumer.
- Open SFTP without requiring a terminal pane.
- Start local, remote, and dynamic forwarding and verify listeners survive pane closure.
- Disconnect a parent node and verify dependent child nodes and jobs transition consistently.
- Reconnect within the grace period without losing unrelated UI state.
- Confirm that task/process cleanup completes after explicit disconnect and application shutdown.
Version History
- 41d8d7d Current 2026-07-25 05:38


