Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions desktop/src-tauri/src/managed_agents/readiness/cli_login.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
use std::path::Path;

use crate::managed_agents::{
discovery::{
classify_runtime, codex_adapter_availability, find_command, resolve_command,
KnownAcpRuntime,
},
discovery::{classify_runtime, codex_adapter_availability, find_command, KnownAcpRuntime},
AcpAvailabilityStatus,
};

Expand Down Expand Up @@ -39,14 +36,16 @@ pub(super) fn requirements(

match availability {
AcpAvailabilityStatus::Available => {
let Some(binary_path) = resolve_command(probe_args[0]) else {
let augmented_path = cli_probe::augmented_path();
let Some(binary_path) =
cli_probe::resolve_probe_command(probe_args[0], augmented_path.as_deref())
else {
return vec![missing_requirement(
probe_args,
setup_copy,
AcpAvailabilityStatus::Available,
)];
};
let augmented_path = cli_probe::augmented_path();
match cli_probe::login_probe(&binary_path, probe_args, augmented_path.as_deref()) {
cli_probe::ProbeOutcome::LoggedIn => vec![],
cli_probe::ProbeOutcome::LoggedOut => vec![missing_requirement(
Expand Down
63 changes: 62 additions & 1 deletion desktop/src-tauri/src/managed_agents/readiness/cli_probe.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::path::Path;
use std::path::{Path, PathBuf};

use crate::managed_agents::runtime::build_augmented_path;

Expand All @@ -21,6 +21,42 @@ pub(crate) fn augmented_path() -> Option<String> {
)
}

/// Resolve a login-probe command with the same PATH precedence used by the
/// managed-agent child process.
///
/// The desktop process may have a stale shim earlier on its ambient PATH than
/// the actual CLI selected by [`augmented_path`]. Probing that shim can report
/// a false logged-out state even though the spawned harness would use a healthy
/// authenticated CLI. Prefer the child PATH on Unix, then fall back to the
/// normal command resolver for explicit paths and platform-specific shims.
pub(crate) fn resolve_probe_command(
command: &str,
augmented_path: Option<&str>,
) -> Option<PathBuf> {
#[cfg(not(windows))]
if !command.contains(std::path::MAIN_SEPARATOR) {
if let Some(path) = augmented_path {
for directory in std::env::split_paths(path) {
let candidate = directory.join(command);
if is_executable_file(&candidate) {
return Some(candidate);
}
}
}
}

crate::managed_agents::resolve_command(command)
}

#[cfg(not(windows))]
fn is_executable_file(path: &Path) -> bool {
use std::os::unix::fs::PermissionsExt;

path.metadata()
.map(|metadata| metadata.is_file() && metadata.permissions().mode() & 0o111 != 0)
.unwrap_or(false)
}

/// Outcome of a CLI login-status probe.
#[derive(Debug, PartialEq, Eq)]
pub(crate) enum ProbeOutcome {
Expand Down Expand Up @@ -164,6 +200,31 @@ mod tests {
);
}

#[cfg(unix)]
#[test]
fn resolve_probe_command_prefers_augmented_child_path() {
use std::fs;
use std::os::unix::fs::PermissionsExt;

let temp = tempfile::tempdir().expect("temp dir");
let preferred_dir = temp.path().join("preferred-bin");
fs::create_dir_all(&preferred_dir).expect("preferred dir");
let preferred = preferred_dir.join("fake-codex");
fs::write(&preferred, "#!/bin/sh\nexit 0\n").expect("write preferred command");
fs::set_permissions(&preferred, fs::Permissions::from_mode(0o755))
.expect("chmod preferred command");
let augmented = std::env::join_paths([preferred_dir.as_path()])
.expect("join augmented PATH")
.to_string_lossy()
.into_owned();

assert_eq!(
super::resolve_probe_command("fake-codex", Some(&augmented)),
Some(preferred),
"readiness must resolve the same command selected by the child PATH"
);
}

#[cfg(unix)]
#[test]
fn login_probe_config_invalid_on_stderr_signal() {
Expand Down
5 changes: 5 additions & 0 deletions desktop/src/features/agents/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,11 @@ with a TypeScript lookup table or an id comparison in a component.
`getAgentAccessOwnerOnly()` is true, every managed agent's access control is
locked to owner-only, including provider-backed agents. A provider backend
does not prove remote execution and must never create a policy carve-out.
12. **CLI readiness uses the child PATH.** Login probes must resolve the CLI
from the same augmented PATH used by the managed-agent child. The desktop's
ambient PATH may contain an older wrapper that resolves a different package
manager prefix and falsely reports that an otherwise healthy CLI is logged
out.

## The tests that enforce this

Expand Down