zeroclaw-labs/zeroclaw · error

CLI channel factory not registered — call register_cli_chann

Error message

CLI channel factory not registered — call register_cli_channel_fn at startup

What it means

Interactive CLI mode obtains its channel from a process-global factory stored in the OnceLock CLI_CHANNEL_FN (crates/zeroclaw-runtime/src/agent/loop_.rs:17). The binary is expected to call register_cli_channel_fn exactly once at startup; entering interactive mode before that registration panics with this message.

Source

Thrown at crates/zeroclaw-runtime/src/agent/agent.rs:3251

                self.config.resolved.max_tool_iterations
            )),
            committed_response,
            new_messages: new_msgs,
        })
    }

    pub async fn run_single(&mut self, message: &str) -> Result<String> {
        self.turn(message).await
    }

    pub async fn run_interactive(&mut self) -> Result<()> {
        println!("🦀 ZeroClaw Interactive Mode");
        println!("Type /quit to exit.\n");

        let (tx, mut rx) = tokio::sync::mpsc::channel(32);
        let cli = crate::agent::loop_::CLI_CHANNEL_FN
            .get()
            .expect("CLI channel factory not registered — call register_cli_channel_fn at startup")(
        );

        let listen_handle = zeroclaw_spawn::spawn!(async move {
            let _ = zeroclaw_api::channel::Channel::listen(&*cli, tx).await;
        });

        while let Some(msg) = rx.recv().await {
            let response = match self.turn(&msg.content).await {
                Ok(resp) => resp,
                Err(e) => {
                    eprintln!("\nError: {e}\n");
                    continue;
                }
            };
            println!("\n{response}\n");
        }

        listen_handle.abort();

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Call zeroclaw_runtime::agent::loop_::register_cli_channel_fn(Box::new(|| Box::new(your_cli_channel))) once during binary startup, before any interactive-mode code can run.
  2. If you are writing a test or custom harness, register a stub channel factory in the test setup.
  3. Compare with the main zeroclaw binary's startup sequence to confirm registration happens before the interactive branch.

Example fix

// before (custom binary)
fn main() {
    zeroclaw_runtime::agent::interactive_main(); // panics: factory not registered
}

// after
fn main() {
    zeroclaw_runtime::agent::loop_::register_cli_channel_fn(Box::new(|| {
        Box::new(zeroclaw_runtime::channel::cli::CliChannel::new())
    }));
    zeroclaw_runtime::agent::interactive_main();
}
Defensive patterns

Strategy: validation

Validate before calling

// Guard before entering interactive mode (custom binaries / tests):
use zeroclaw_runtime::agent::loop_::{CLI_CHANNEL_FN, register_cli_channel_fn};
if CLI_CHANNEL_FN.get().is_none() {
    register_cli_channel_fn(Box::new(|| Box::new(my_cli_channel())));
}
// Now safe to call the interactive entry point.

Try / catch

std::panic::catch_unwind(|| agent::interactive_main())
    .map_err(|_| anyhow::anyhow!("interactive mode unavailable: CLI channel factory not registered"))?;

Prevention

When it happens

Trigger: Invoking the interactive-mode entry point (agent.rs:3251 or loop_.rs:2175) inside a process that never called register_cli_channel_fn: a custom binary embedding the zeroclaw-runtime crate, an integration test driving interactive mode, or a startup-order change that defers registration.

Common situations: Embedding zeroclaw-runtime in your own executable and reusing its interactive loop; writing tests that exercise the agent loop without the full binary bootstrap; a refactor that moves the register_cli_channel_fn call after the interactive-mode branch.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/79ffdcf31cdd9fea. Report an issue: GitHub.