Hmbown/CodeWhale · critical
reviewed plugin stage changed before stdio launch
Error message
reviewed plugin stage changed before stdio launch
What it means
TOCTOU guard at stdio launch: the staged manifest is re-validated and its content_hash and capability_hash are compared against the hashes recorded when the plugin was reviewed; any mismatch aborts the launch. It fires when plugin files changed on disk after review - tampering, but also legitimate edits or version bumps that bypassed the review flow. The reviewed approval never covers the changed bytes.
Source
Thrown at crates/tui/src/mcp.rs:667
server_name: &str,
command: &str,
args: &[String],
cwd: Option<&Path>,
) -> Result<ReviewedStdioLaunch> {
self.validate_before_stdio_spawn(server_name)?;
let staged_root = self
.authority
.staged_manifest
.parent()
.context("reviewed plugin stage manifest has no parent")?;
let validated = crate::plugins::manifest::PluginManifest::validate_from_path(
&self.authority.staged_manifest,
)
.map_err(|_| anyhow::anyhow!("reviewed plugin stage could not be opened for launch"))?;
if validated.content_hash != self.authority.content_hash
|| validated.capability_hash != self.authority.capability_hash
{
anyhow::bail!("reviewed plugin stage changed before stdio launch");
}
let mut launch = ReviewedStdioLaunch {
command: std::ffi::OsString::from(command),
args: args.iter().map(std::ffi::OsString::from).collect(),
cwd: cwd.map(Path::to_path_buf),
opened_files: Vec::new(),
#[cfg(unix)]
cwd_fd: None,
};
if Path::new(command).is_absolute() {
launch.bind_command(staged_root, Path::new(command), &validated.file_hashes)?;
}
for (index, argument) in args.iter().enumerate() {
let path = Path::new(argument);
if path.is_absolute() && path.starts_with(staged_root) && path.is_file() {
launch.args[index] = launch.bind_file(staged_root, path, &validated.file_hashes)?;
}View on GitHub (pinned to 8880682c63)
Solutions
- If the change is intentional: /plugin reload, inspect /plugin show <name>, repeat the displayed trust command, then /plugin enable <name> so the new hashes are reviewed
- Stop editing staged or bundled files in place; publish a new bundle version instead
- If nothing was changed by you, audit what modified the stage (mtimes, audit logs) before re-trusting - treat it as possible tampering
Example fix
# error: reviewed plugin stage changed before stdio launch /plugin reload /plugin show my-plugin # confirm the new hashes are expected # repeat the displayed trust command, then: /plugin enable my-plugin
Defensive patterns
Strategy: retry
Validate before calling
// Detect drift before launch: re-hash the stage and compare with the review record
fn stage_matches(review: &ReviewRecord, staged: &std::path::Path) -> bool {
PluginManifest::validate_from_path(staged)
.map(|v| v.content_hash == review.content_hash && v.capability_hash == review.capability_hash)
.unwrap_or(false)
} Try / catch
// Never bypass: restage + re-review is the only sanctioned recovery
if err.to_string().contains("changed before stdio launch") {
reload_and_retrust(&plugin)?; // /plugin reload + trust + /plugin enable
return retry_launch_once();
} Prevention
- Never edit plugin bundles under the stage root; version and republish instead
- Re-run review after any plugin update, however small
- Investigate unexpected hash mismatches before re-trusting; they can indicate tampering
- Keep reviewed bundles on immutable (read-only) storage between reviews
When it happens
Trigger: Editing files inside the plugin stage or reviewed source between approval and launch, an update overwriting the bundle without redoing trust, external processes (sync tools, CI deploys) touching staged files, or deliberate tampering.
Common situations: Developers editing plugin sources in place, CI redeploying bundles under running sessions, dropbox-style sync touching staged files, supply-chain style attempts to swap code after review.
Related errors
- reviewed plugin executable bytes changed before spawn
- reviewed plugin stage could not be opened for launch
- reviewed plugin stdio cwd escaped its staged root
- Refusing to {operation} MCP server '{server_name}' from plug
- Refusing MCP server '{server_name}': its remote endpoint no
AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16).
Data as JSON: /api/errors/18fd4457fd300723.
Report an issue: GitHub.