BloopAI/vibe-kanban · error

Channel not found

Error message

Channel not found

What it means

In the embedded SSH server, spawn_stdio_session removes the channel's state from the handler's channels map before wiring up a shell/exec stdio session. If the channel id is absent from the map, there is no pending channel to promote, so it fails with 'Channel not found'. This indicates the request references a channel that was never opened or was already consumed/closed.

Source

Thrown at crates/embedded-ssh/src/handler.rs:58

    pub fn new(relay_signing: RelaySigningService) -> Self {
        Self {
            relay_signing,
            channels: HashMap::new(),
            tcpip_forwards: HashMap::new(),
        }
    }

    fn spawn_stdio_session(
        &mut self,
        channel_id: ChannelId,
        command: Option<&str>,
        session: &mut Session,
    ) -> Result<(), anyhow::Error> {
        tracing::debug!("Spawning stdio session (no PTY)");
        let state = self
            .channels
            .remove(&channel_id)
            .ok_or_else(|| anyhow::anyhow!("Channel not found"))?;

        let env = match state {
            ChannelState::Pending { channel: _, env } => env,
            ChannelState::Active { .. } => {
                anyhow::bail!("Channel already has an active session");
            }
        };

        let shell = std::env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string());
        let mut cmd = Command::new(shell);
        match command {
            Some(cmd_str) => {
                cmd.arg("-c");
                cmd.arg(cmd_str);
            }
            None => {
                // Non-interactive shell reading commands from stdin.
                cmd.arg("-s");

View on GitHub (pinned to 4deb7eca8f)

Solutions

  1. Ensure each SSH channel runs at most one shell/exec request; open a new channel for each command.
  2. Check that channel_open_handler registered the channel in self.channels before the client sends exec/shell.
  3. Add logging to confirm the channel_id used in the request matches the one created during open; look for id mismatch or reuse.
  4. If a race with channel close is suspected, guard the handler so close/eof events and spawn_stdio_session don't consume the entry concurrently.

Example fix

// before (client reusing one channel)
channel.exec("ls");
channel.exec("pwd"); // second exec -> Channel not found
// after
channel.exec("ls");
let channel2 = session.channel_open()?;
channel2.exec("pwd");
Defensive patterns

Strategy: validation

Validate before calling

// client side: open a fresh channel per request
let channel = session.channel_open()?; // register before exec/shell
channel.exec("command")?; // exactly one request per channel

Try / catch

match handler.spawn_stdio_session(session, channel_id, ...) {
    Ok(()) => {},
    Err(e) if e.to_string() == "Channel not found" => {
        tracing::debug!(channel_id, "exec on unknown/closed channel; ignoring");
        // send SSH channel failure rather than tearing down the session
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: A shell_request or exec_request arrives with a channel_id not present in self.channels — e.g. the channel was already removed by a previous spawn (double exec on one channel), a close/EOF event removed it first, or a client sends exec on a channel that failed to open.

Common situations: Buggy or aggressive SSH clients issuing exec/shell twice on the same channel, race between channel close and request handling, or a server restart clearing in-memory channel state while the client still believes the channel is open.

Related errors


AI-assisted analysis of BloopAI/vibe-kanban@4deb7eca8f (2026-08-29). Data as JSON: /api/errors/174d798c14af55fe. Report an issue: GitHub.