Skip to content

fix: restore only active WASM channels at startup#2562

Open
henrypark133 wants to merge 2 commits intostagingfrom
fix-reconnect-loop
Open

fix: restore only active WASM channels at startup#2562
henrypark133 wants to merge 2 commits intostagingfrom
fix-reconnect-loop

Conversation

@henrypark133
Copy link
Copy Markdown
Collaborator

Summary

Fixes the WASM channel boot-restore bug where startup treated every channel discovered on disk as active.

Boot now restores only channels persisted in activated_channels, while keeping installed-but-inactive WASM channels discoverable in /api/extensions with active: false.

Fixes #2556

Root Cause

Startup was conflating three different states:

  • installed on disk
  • restore on restart
  • actually running in the current process

setup_wasm_channels(...) loaded every discovered channel from wasm_channels_dir, ChannelManager started them, and ExtensionManager::set_active_channels(...) then reported discovery as runtime-active state.

What Changed

Startup restore semantics

  • Load persisted activated_channels before WASM channel setup in main.rs.
  • Preserve the raw persisted list for relay restore logic.
  • Derive a canonicalized WASM-only restore set from persisted entries.
  • Pass that canonical persisted-active set into setup_wasm_channels(...).
  • Restore/register only the channels in that persisted-active WASM set during startup.
  • Keep active_channel_names aligned with channels actually running now, not channels merely found on disk.

No-persistence behavior

  • setup_wasm_channels(...) now accepts an optional startup-active filter.
  • When persistence is available, startup restores only persisted-active WASM channels.
  • When persistence is unavailable (for example no settings store / no-DB mode), startup falls back to restoring all installed WASM channels, preserving previous supported behavior.

Legacy relay-name compatibility

  • Do not canonicalize the mixed persisted active list up front.
  • Filter relay entries first using is_relay_channel(...).
  • Canonicalize only the non-relay WASM subset.
  • This preserves legacy relay names such as slack-relay so relay restore/auth lookup continues to work.

Follow-up fix

  • pending_gate_extension_name(...) now mirrors auth-manager install-action resolution even when auth_manager is absent, so post-install auth gates continue to show the extension name (telegram) instead of the underlying credential name (telegram_bot_token).

Test hardening

  • Registry artifact tests and the buildable-install extension-manager test now clear ambient CARGO_TARGET_DIR under the repo env mutex so they do not depend on the caller's build environment.

Files

  • src/main.rs
  • src/channels/wasm/setup.rs
  • src/extensions/manager.rs
  • src/channels/web/server.rs
  • src/registry/artifacts.rs

Tests

Ran:

  • cargo fmt --check
  • git diff --check
  • cargo clippy --all-targets --all-features -- -D warnings
  • cargo test --lib channels::web::server::tests::pending_gate_extension_name_uses_install_parameters_for_post_install_auth -- --exact --nocapture
  • cargo test --lib registry::artifacts::tests::test_resolve_target_dir_default -- --exact --nocapture
  • cargo test --lib registry::artifacts::tests::test_find_any_wasm_artifact_not_found -- --exact --nocapture
  • cargo test --lib extensions::manager::tests::ensure_extension_ready_auto_installs_registry_wasm_tool_on_explicit_activate -- --exact --nocapture
  • cargo test --lib channels::wasm::setup::tests::register_startup_loaded_channels_only_restores_persisted_active_channels -- --exact --nocapture
  • cargo test --lib channels::wasm::setup::tests::register_startup_loaded_channels_without_persistence_restores_all_channels -- --exact --nocapture

Behavior After This Change

  • Installed WASM channels are still discovered from disk.
  • Only persisted-active WASM channels are restored on restart when settings persistence exists.
  • Installed-but-inactive WASM channels remain inactive until explicitly activated.
  • Inactive channels do not bind startup webhook routes or contribute startup hook registration.
  • No-DB mode still restores installed WASM channels as before.

Copilot AI review requested due to automatic review settings April 17, 2026 02:02
@github-actions github-actions bot added scope: channel/web Web gateway channel scope: channel/wasm WASM channel runtime scope: extensions Extension management size: L 200-499 changed lines risk: medium Business logic, config, or moderate-risk modules contributor: core 20+ merged PRs labels Apr 17, 2026
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the WASM channel setup process to support the restoration of persisted active channels. It introduces a registration context for startup, improves the discovery and loading logic to filter by active status, and adds comprehensive unit tests. Review feedback highlights opportunities to remove redundant logging and logic checks in the setup flow, as well as a performance optimization in the main entry point to avoid unnecessary heap allocations when processing channel names.

Comment thread src/channels/wasm/setup.rs Outdated
Comment on lines +151 to +153
for (path, err) in &load_errors {
tracing::warn!("Failed to load WASM channel {}: {}", path.display(), err);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

This block causes double logging of WASM channel load failures. These errors are already logged with more detailed context (including the channel name and specific error message) during the loading loop at lines 127-132.

Comment on lines +203 to +208
if !context
.startup_active_channel_names
.is_none_or(|active_names| active_names.contains(loaded.name()))
{
continue;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

This check is redundant. The loaded_channels vector is derived from startup_entries, which is already filtered by startup_active_channel_names in the caller function setup_wasm_channels (lines 106-114). Removing this redundant check simplifies the loop logic.

Comment thread src/main.rs Outdated
Comment on lines +127 to +134
let mut wasm_channels = Vec::new();
for name in persisted_active_channels {
if persisted_active_relay_channels.contains(name) {
continue;
}
wasm_channels.push(name.clone());
}
normalize_persisted_wasm_channel_names(wasm_channels)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

This logic can be optimized by using an iterator filter instead of allocating an intermediate Vec. Since normalize_persisted_wasm_channel_names accepts any IntoIterator, we can pass the filtered iterator directly. This avoids unnecessary heap allocations, which is important for performance in WASM environments.

    let wasm_channels = persisted_active_channels
        .iter()
        .filter(|name| !persisted_active_relay_channels.contains(*name));
    normalize_persisted_wasm_channel_names(wasm_channels)
References
  1. To improve performance in WASM, avoid unnecessary heap allocations. For instance, when processing string parts, use iterators directly instead of collecting them into a Vec.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 fixes a startup restore bug for WASM channels by separating “installed on disk” from “active/running,” ensuring only persisted-active WASM channels are restored at boot while still listing installed-but-inactive channels as discoverable but inactive.

Changes:

  • Load persisted activated_channels earlier during startup and pass a WASM-only persisted-active set into WASM channel setup so boot only restores those channels (no-DB mode still restores all installed channels).
  • Align extension active-state reporting (active_channel_names) with channels actually running in-process rather than discovered-on-disk.
  • Harden tests by clearing ambient CARGO_TARGET_DIR under the repo’s env mutex, and adjust pending-gate extension-name resolution when auth_manager is absent.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/main.rs Computes persisted-active WASM restore set (with relay compatibility) and wires startup restore flow accordingly.
src/channels/wasm/setup.rs Adds optional startup-active filter; loads/registers only startup-selected WASM channels while keeping webhook router available.
src/extensions/manager.rs Clarifies semantics of active_channel_names; test hardening for env isolation (CARGO_TARGET_DIR).
src/channels/web/server.rs Improves pending auth-gate extension name inference without auth_manager; adds regression test for inactive WASM listing.
src/registry/artifacts.rs Hardens artifact tests by clearing CARGO_TARGET_DIR under the env mutex.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/channels/web/server.rs Outdated

if matches!(
tool_name,
"tool_install" | "tool-install" | "tool_activate" | "tool_auth"
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

pending_gate_extension_name() handles both underscore and hyphen variants for tool_install, but the new fallback branch only matches underscore forms for tool_activate and tool_auth. Elsewhere in the codebase both hyphenated names exist (e.g. tool-activate, tool-auth), so this will fail to resolve the extension name for those tool names when auth_manager is absent. Consider including the hyphenated variants in the matches!() list (or normalizing tool_name once before matching).

Suggested change
"tool_install" | "tool-install" | "tool_activate" | "tool_auth"
"tool_install"
| "tool-install"
| "tool_activate"
| "tool-activate"
| "tool_auth"
| "tool-auth"

Copilot uses AI. Check for mistakes.
Comment thread src/channels/wasm/setup.rs Outdated
Comment on lines +127 to +131
tracing::warn!(
channel = %name,
path = %wasm_path.display(),
error = %err,
"Failed to load persisted-active WASM channel at startup"
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

This warning message is emitted for any startup load failure, including the no-persistence path where startup_active_channel_names is None and all installed channels are attempted. In that case, the wording "persisted-active" is inaccurate/misleading. Consider making the message conditional on startup_active_channel_names.is_some() (or using more general wording like "Failed to load WASM channel at startup").

Suggested change
tracing::warn!(
channel = %name,
path = %wasm_path.display(),
error = %err,
"Failed to load persisted-active WASM channel at startup"
let warning_message = if startup_active_channel_names.is_some() {
"Failed to load persisted-active WASM channel at startup"
} else {
"Failed to load WASM channel at startup"
};
tracing::warn!(
channel = %name,
path = %wasm_path.display(),
error = %err,
"{warning_message}"

Copilot uses AI. Check for mistakes.
Comment thread src/channels/wasm/setup.rs Outdated
Comment on lines +151 to +153
for (path, err) in &load_errors {
tracing::warn!("Failed to load WASM channel {}: {}", path.display(), err);
}
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

setup_wasm_channels() logs load failures twice: once with structured fields inside the load loop and again here via load_errors. This will produce duplicated warnings for each failing channel at startup. Consider removing this second loop or downgrading one of the logs (e.g. keep the structured warn and make this a debug summary).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

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

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: 1d568ee17b

ℹ️ 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 src/main.rs Outdated
Comment on lines +93 to +94
match ironclaw::extensions::naming::canonicalize_extension_name(name.as_ref()) {
Ok(canonical) => {
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 persisted WASM channel names intact during restore

This canonicalization step drops persisted channel names that were previously valid at runtime (for example names with uppercase letters, dots, or double underscores), because canonicalize_extension_name is stricter than the WASM channel loader’s name checks. In that case the channel remains in activated_channels but is filtered out of persisted_active_wasm_channels, so startup never reloads it and it silently stops restoring after restart. Consider preserving persisted names as-is for WASM restore filtering (or validating with the same rules as WasmChannelLoader).

Useful? React with 👍 / 👎.

@github-actions github-actions bot added size: XL 500+ changed lines and removed size: L 200-499 changed lines labels Apr 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: core 20+ merged PRs risk: medium Business logic, config, or moderate-risk modules scope: channel/wasm WASM channel runtime scope: channel/web Web gateway channel scope: extensions Extension management size: XL 500+ changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

WASM channels: do not auto-start installed but inactive channels at boot

2 participants