BigPizzaV3/CodexPlusPlus · error · anyhow::Error
Codex injection failed
Error message
Codex injection failed
What it means
retry_injection (crates/codex-plus-core/src/launcher.rs:2369) tries try_inject up to 20 times with 500 ms sleeps while the Codex app warms up its DevTools target. Every Err iteration stores the error in last_error, so the final Err normally carries the real underlying failure (CDP connect refused, no injectable page target, etc.). The bare 'Codex injection failed' only materializes when the loop recorded no error — structurally unreachable unless the loop body is refactored, so encountering it usually means a forked retry loop or a logic regression.
Source
Thrown at crates/codex-plus-core/src/launcher.rs:2380
inspector_port,
extra_args,
)),
process_id: None,
})
}
async fn retry_injection(debug_port: u16, helper_port: u16) -> anyhow::Result<()> {
let mut last_error = None;
for _ in 0..20 {
match try_inject(debug_port, helper_port).await {
Ok(()) => return Ok(()),
Err(error) => {
last_error = Some(error);
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
}
}
}
Err(last_error.unwrap_or_else(|| anyhow::anyhow!("Codex injection failed")))
}
pub async fn check_and_reinject_bridge(debug_port: u16, helper_port: u16) -> bool {
check_and_reinject_bridge_inner(debug_port, helper_port, false, None).await
}
pub fn browser_identity_changed(previous: Option<&str>, current: &str) -> bool {
previous.is_some_and(|previous| previous != current)
}
fn launcher_target_alive(has_codex_process: bool, cdp_available: bool) -> bool {
has_codex_process || cdp_available
}
fn should_probe_launcher_cdp(is_windows: bool, has_codex_process: bool) -> bool {
is_windows && !has_codex_process
}
View on GitHub (pinned to 1f431ae49b)
Solutions
- Read the full error chain: in stock code you get the last try_inject error (e.g. 'No injectable Codex page target found') — fix that cause first: ensure Codex fully started and its remote-debugging port is reachable
- Increase startup patience by retrying launch/injection at the caller level (check_and_reinject_bridge retries later) rather than editing the 20x500ms budget
- If you maintain a fork, preserve `last_error = Some(error)` in the Err arm so the real cause is always reported
- Check the debug port: curl http://127.0.0.1:<debug_port>/json/list and look for an app://--/index.html page target with a webSocketDebuggerUrl
Example fix
// before (fork regression that loses the cause)
for _ in 0..20 {
match try_inject(debug_port, helper_port).await {
Ok(()) => return Ok(()),
Err(_) => { tokio::time::sleep(Duration::from_millis(500)).await; }
}
}
Err(last_error.unwrap_or_else(|| anyhow!("Codex injection failed"))) // fires with no cause
// after
for _ in 0..20 {
match try_inject(debug_port, helper_port).await {
Ok(()) => return Ok(()),
Err(error) => { last_error = Some(error); tokio::time::sleep(Duration::from_millis(500)).await; }
}
}
Err(last_error.unwrap_or_else(|| anyhow!("Codex injection failed"))) Defensive patterns
Strategy: retry
Validate before calling
// Pre-flight the CDP endpoint the injector needs
async fn injectable_target_ready(debug_port: u16) -> bool {
crate::cdp::list_targets(debug_port).await
.map(|t| crate::cdp::pick_injectable_codex_page_target(&t).is_ok())
.unwrap_or(false)
} Type guard
fn is_injection_failure(e: &anyhow::Error) -> bool {
let msg = e.to_string();
msg.contains("Codex injection failed") || msg.contains("No injectable Codex page target found")
} Try / catch
// Retry at the caller level with backoff; injection is idempotent via install_bridge
let mut attempts = 0;
loop {
match check_and_reinject_bridge(debug_port, helper_port).await {
true => break,
false if attempts < 5 => { attempts += 1; tokio::time::sleep(Duration::from_secs(2)).await; }
false => { tracing::error!("bridge injection gave up after {attempts} rounds"); break; }
}
} Prevention
- Rely on the watchdog's later re-injection (check_and_reinject_bridge) instead of failing hard on first-launch injection
- Verify http://127.0.0.1:<debug_port>/json/list shows an app://--/index.html page before expecting injection to succeed
- If maintaining a fork, always preserve last_error assignment in retry loops so real causes surface
- Ensure the Codex app is fully started (remote debugging port listening) before injecting
When it happens
Trigger: The 20-attempt loop exhausts (Codex never exposes an injectable app:// page within ~10 s) — the surfaced error is then the last try_inject error, not this one; this exact message appears only if the match arms were modified so Err no longer sets last_error (e.g. a continue added, or the loop range changed to 0).
Common situations: Slow machines or antivirus-delayed app startup causing all 20 attempts to fail (you see the real cause instead); forks that rewrote retry_injection; race where the app exits during startup and the error slot is bypassed.
Related errors
- selected CDP target has no websocket URL
- Page.captureScreenshot returned no image data
- CDP WebSocket URL must include an explicit port
- browser WebSocket URL has no path
- CDP WebSocket URL has no host
AI-assisted analysis of BigPizzaV3/CodexPlusPlus@1f431ae49b (2026-08-16).
Data as JSON: /api/errors/1545d685c47d78fe.
Report an issue: GitHub.