BigPizzaV3/CodexPlusPlus · error · anyhow::Error

provider sync requires launcher hooks with codex-plus-data i

Error message

provider sync requires launcher hooks with codex-plus-data integration

What it means

codex-plus-core's default LaunchHooks implementation is a stub: DefaultLaunchHooks::run_provider_sync (crates/codex-plus-core/src/launcher.rs:564) unconditionally bails with this message. The real implementation lives one layer up in apps/codex-plus-launcher/src/main.rs:328, where LauncherHooks::run_provider_sync delegates to codex_plus_data::run_provider_sync to merge ~/.codex provider auth into the manager. Seeing this error means the launcher was constructed with DefaultLaunchHooks::shared() instead of a data-integrated hooks object, so the operation is genuinely unavailable, not failed.

Source

Thrown at crates/codex-plus-core/src/launcher.rs:565

            Some(settings.codex_app_path.as_str()),
        )
        .ok_or_else(|| anyhow::anyhow!("Codex App directory not found"))
    }

    fn select_debug_port(&self, requested: u16) -> u16 {
        crate::ports::select_packaged_codex_debug_port(requested)
    }

    fn select_helper_port(&self, requested: u16) -> u16 {
        crate::ports::select_platform_loopback_port(requested)
    }

    async fn load_settings(&self) -> anyhow::Result<BackendSettings> {
        SettingsStore::default().load()
    }

    async fn run_provider_sync(&self) -> anyhow::Result<()> {
        anyhow::bail!("provider sync requires launcher hooks with codex-plus-data integration")
    }

    async fn run_remote_control_session_recovery(&self) -> anyhow::Result<()> {
        anyhow::bail!(
            "Remote Control session recovery requires launcher hooks with codex-plus-data integration"
        )
    }

    fn remote_control_session_recovery_is_safe_to_run(&self) -> bool {
        crate::watcher::find_session_index_cleanup_blocking_processes().is_empty()
    }

    async fn apply_active_relay_profile(&self, settings: &BackendSettings) -> anyhow::Result<()> {
        if !settings.relay_profiles_enabled {
            return Ok(());
        }
        let profile = settings.active_relay_profile();
        let home = crate::relay_config::default_codex_home_dir();

View on GitHub (pinned to 1f431ae49b)

Solutions

  1. Use the launcher binary in apps/codex-plus-launcher (LauncherHooks implements run_provider_sync via codex_plus_data::run_provider_sync) instead of a core-only embedding
  2. If you embed codex-plus-core in your own binary, implement LaunchHooks yourself and call codex_plus_data::run_provider_sync(None) inside run_provider_sync, mirroring apps/codex-plus-launcher/src/main.rs:328
  3. If the operation is genuinely optional in your context, treat this error as non-fatal: log it and continue (the launcher app ignores sync failures rather than aborting launch)
  4. In tests, inject a mock LaunchHooks whose run_provider_sync returns Ok(()) so the stub bail never fires

Example fix

// before (core-only wiring hits the stub)
let hooks = DefaultLaunchHooks::shared();
let launcher = CodexLauncher::with_hooks(hooks);
launcher.launch(...).await?; // run_provider_sync bails

// after (data-backed hooks)
struct DataBackedHooks;
#[async_trait(?Send)]
impl LaunchHooks for DataBackedHooks {
    async fn run_provider_sync(&self) -> anyhow::Result<()> {
        tokio::task::spawn_blocking(|| codex_plus_data::run_provider_sync(None))
            .await
            .map_err(|e| anyhow::anyhow!("provider sync task failed: {e}"))??;
        Ok(())
    }
    // ...delegate remaining methods to DefaultLaunchHooks
}
let launcher = CodexLauncher::with_hooks(Arc::new(DataBackedHooks));
Defensive patterns

Strategy: fallback

Validate before calling

// Before launching, verify the hooks you wired are data-integrated
fn hooks_support_provider_sync(hooks: &dyn LaunchHooks) -> bool {
    // probe cheaply: default stub always fails; real impl succeeds/idempotently no-ops
    // simplest: assert wiring at construction time in your binary
    true
}
// Prefer compile-time wiring instead of runtime probing:
// construct the launcher only via your DataBackedHooks type.

Type guard

// Rust has no runtime trait-downcast guard needed if you own the type;
// narrow via as_any when mixing hook sources:
impl LaunchHooks for DataBackedHooks {
    fn as_any(&self) -> &dyn std::any::Any { self }
}
fn is_data_backed(hooks: &dyn LaunchHooks) -> bool {
    hooks.as_any().downcast_ref::<DataBackedHooks>().is_some()
}

Try / catch

match hooks.run_provider_sync().await {
    Ok(()) => {}
    Err(e) if e.to_string().contains("requires launcher hooks with codex-plus-data integration") => {
        tracing::warn!("provider sync unavailable in this embedding; skipping");
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling CodexLauncher::launch (or any flow that invokes LaunchHooks::run_provider_sync) while the launcher was built with DefaultLaunchHooks — typically in unit/integration tests inside codex-plus-core, in a custom binary that embeds the core crate directly, or in the Tauri manager app if it forgets to install its LauncherHooks wrapper.

Common situations: Writing new tests against launcher flows using the default hooks; building a downstream binary that depends on codex-plus-core only (no codex-plus-data feature); refactoring that accidentally swaps LauncherHooks back to DefaultLaunchHooks::shared(); version upgrades that rename the hooks trait so the override impl silently stops applying (no compile error if trait method signature changed and the impl was dropped).

Related errors


AI-assisted analysis of BigPizzaV3/CodexPlusPlus@1f431ae49b (2026-08-16). Data as JSON: /api/errors/f741815aff5c5558. Report an issue: GitHub.