fix: detect linuxbrew ai agent binaries

This commit is contained in:
lucaronin
2026-04-30 15:01:35 +02:00
parent 3c14d60be7
commit d53c3fa7e5
6 changed files with 209 additions and 25 deletions

View File

@@ -1,7 +1,7 @@
use crate::ai_agents::{AiAgentPermissionMode, AiAgentStreamEvent};
use serde::Deserialize;
use std::io::BufRead;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use std::process::{Command, ExitStatus};
#[derive(Debug, Clone, Deserialize)]
@@ -47,6 +47,51 @@ pub(crate) fn version_for_binary(binary: &PathBuf) -> Option<String> {
.map(|output| String::from_utf8_lossy(&output.stdout).trim().to_string())
}
pub(crate) fn find_executable_binary_candidate(
candidates: Vec<PathBuf>,
agent_label: &str,
) -> Result<Option<PathBuf>, String> {
let mut first_unusable_candidate = None;
for candidate in candidates {
if !candidate.exists() {
continue;
}
if is_executable_file(&candidate) {
return Ok(Some(candidate));
}
if first_unusable_candidate.is_none() {
first_unusable_candidate = Some(candidate);
}
}
match first_unusable_candidate {
Some(candidate) => Err(format!(
"{agent_label} binary found at {} but it is not executable. Fix the file permissions or reinstall the CLI.",
candidate.display()
)),
None => Ok(None),
}
}
fn is_executable_file(path: &Path) -> bool {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::metadata(path)
.map(|metadata| metadata.is_file() && metadata.permissions().mode() & 0o111 != 0)
.unwrap_or(false)
}
#[cfg(not(unix))]
{
path.is_file()
}
}
pub(crate) fn parse_json_line(
line: Result<String, std::io::Error>,
) -> Result<Option<serde_json::Value>, String> {
@@ -183,4 +228,42 @@ mod tests {
let error = parse_json_line(Err(std::io::Error::other("broken pipe"))).unwrap_err();
assert!(error.contains("broken pipe"));
}
#[cfg(unix)]
#[test]
fn executable_binary_candidate_skips_unusable_file_when_later_candidate_works() {
use std::os::unix::fs::PermissionsExt;
let dir = tempfile::tempdir().unwrap();
let unusable = dir.path().join("codex-unusable");
let executable = dir.path().join("codex");
std::fs::write(&unusable, "#!/bin/sh\n").unwrap();
std::fs::write(&executable, "#!/bin/sh\n").unwrap();
std::fs::set_permissions(&unusable, std::fs::Permissions::from_mode(0o644)).unwrap();
std::fs::set_permissions(&executable, std::fs::Permissions::from_mode(0o755)).unwrap();
let found =
find_executable_binary_candidate(vec![unusable, executable.clone()], "Codex CLI")
.unwrap();
assert_eq!(found, Some(executable));
}
#[cfg(unix)]
#[test]
fn executable_binary_candidate_reports_unusable_file_when_no_candidate_works() {
use std::os::unix::fs::PermissionsExt;
let dir = tempfile::tempdir().unwrap();
let unusable = dir.path().join("opencode");
std::fs::write(&unusable, "#!/bin/sh\n").unwrap();
std::fs::set_permissions(&unusable, std::fs::Permissions::from_mode(0o644)).unwrap();
let error =
find_executable_binary_candidate(vec![unusable.clone()], "OpenCode CLI").unwrap_err();
assert!(error.contains("OpenCode CLI binary found"));
assert!(error.contains(&unusable.display().to_string()));
assert!(error.contains("not executable"));
}
}