Repository navigation
Port upstream 0.65.0: honor unusable Antigravity CLI path overrides #615
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
6cc2981
1bda0d0
c3688ef
038d611
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,187 @@ | ||
| //! `agy` executable resolution (upstream `BinaryLocator.resolveAntigravityBinary`). | ||
| //! | ||
| //! A set `ANTIGRAVITY_CLI_PATH` is authoritative: background refreshes must not | ||
| //! discover and launch another `agy` (which could start an interactive login) | ||
| //! when the explicit override is empty, missing or not a file. Only an unset | ||
| //! variable enables discovery through PATH and the known install directories. | ||
|
|
||
| use std::path::PathBuf; | ||
|
|
||
| use crate::core::ProviderError; | ||
|
|
||
| pub(super) fn locate_agy_binary() -> Result<Option<PathBuf>, ProviderError> { | ||
| resolve_agy_binary( | ||
| std::env::var_os("ANTIGRAVITY_CLI_PATH").map(PathBuf::from), | ||
| || { | ||
| agy_binary_candidates( | ||
| which::which("agy").ok(), | ||
| std::env::var_os("LOCALAPPDATA").map(PathBuf::from), | ||
| dirs::home_dir(), | ||
| ) | ||
| }, | ||
| ) | ||
| } | ||
|
|
||
| fn validate_agy_binary_override( | ||
| explicit: Option<PathBuf>, | ||
| ) -> Result<Option<PathBuf>, ProviderError> { | ||
| let Some(path) = explicit else { | ||
| return Ok(None); | ||
| }; | ||
| if path.is_file() { | ||
| Ok(Some(path)) | ||
| } else { | ||
| Err(ProviderError::NotInstalled(format!( | ||
| "ANTIGRAVITY_CLI_PATH is set but does not point to a usable agy file: {}. Fix or unset the variable; automatic CLI discovery is disabled while it is set.", | ||
| path.display() | ||
| ))) | ||
| } | ||
| } | ||
|
|
||
| fn resolve_agy_binary<F>( | ||
| explicit: Option<PathBuf>, | ||
| discover: F, | ||
| ) -> Result<Option<PathBuf>, ProviderError> | ||
| where | ||
| F: FnOnce() -> Vec<PathBuf>, | ||
| { | ||
| if let Some(path) = validate_agy_binary_override(explicit)? { | ||
| return Ok(Some(path)); | ||
| } | ||
| Ok(discover().into_iter().find(|path| path.is_file())) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: sed -n '45,69p' rust/src/providers/antigravity/cli_resolution.rs
rg -n 'which::|ANTIGRAVITY_CLI_PATH|agy/bin|local/bin/agy' rust/src/providers/antigravity rust/Cargo.tomlRepository: nesszer/Win-CodexBar Length of output: 1568 🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- cli_resolution.rs ---'
cat -n rust/src/providers/antigravity/cli_resolution.rs | sed -n '1,155p'
printf '%s\n' '--- provider environment and install references ---'
rg -n -C 3 'local_app_data|LOCALAPPDATA|\.local/bin|agy\.exe|which::which|ANTIGRAVITY_CLI_PATH|install|PATH' rust/src/providers/antigravity rust/Cargo.toml Cargo.lock README.md .github 2>/dev/null | sed -n '1,260p'
printf '%s\n' '--- which dependency declarations ---'
rg -n -C 3 '(^|[[:space:]])which([[:space:]]|=)|which =' rust/Cargo.toml Cargo.lockRepository: nesszer/Win-CodexBar Length of output: 24788 🌐 Web query:
💡 Result: Filter non-runnable fixed-path candidates.
🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| fn agy_binary_candidates( | ||
| path_lookup: Option<PathBuf>, | ||
| local_app_data: Option<PathBuf>, | ||
| home: Option<PathBuf>, | ||
| ) -> Vec<PathBuf> { | ||
| let mut candidates = Vec::new(); | ||
| if let Some(path) = path_lookup { | ||
| candidates.push(path); | ||
| } | ||
| if let Some(root) = local_app_data { | ||
| candidates.push(root.join("agy").join("bin").join("agy.exe")); | ||
| } | ||
| if let Some(root) = home { | ||
| candidates.push(root.join(".local").join("bin").join(if cfg!(windows) { | ||
| "agy.exe" | ||
| } else { | ||
| "agy" | ||
| })); | ||
| } | ||
| candidates | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
|
|
||
| #[test] | ||
| fn candidates_prefer_path_then_known_installs() { | ||
| let path_lookup = PathBuf::from(r"C:\path\agy.exe"); | ||
| let local_app_data = PathBuf::from(r"C:\Users\test\AppData\Local"); | ||
| let home = PathBuf::from(r"C:\Users\test"); | ||
|
|
||
| let candidates = agy_binary_candidates( | ||
| Some(path_lookup.clone()), | ||
| Some(local_app_data.clone()), | ||
| Some(home.clone()), | ||
| ); | ||
|
|
||
| assert_eq!(candidates[0], path_lookup); | ||
| assert_eq!( | ||
| candidates[1], | ||
| local_app_data.join("agy").join("bin").join("agy.exe") | ||
| ); | ||
| assert_eq!( | ||
| candidates[2], | ||
| home.join(".local") | ||
| .join("bin") | ||
| .join(if cfg!(windows) { "agy.exe" } else { "agy" }) | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn usable_override_is_selected_without_discovery() { | ||
| let temp = tempfile::tempdir().expect("temporary directory"); | ||
| let override_path = temp.path().join("configured-agy.exe"); | ||
| std::fs::write(&override_path, b"test executable placeholder").expect("write fixture"); | ||
|
|
||
| let resolved = resolve_agy_binary(Some(override_path.clone()), || { | ||
| panic!("a configured override must not trigger automatic discovery") | ||
| }) | ||
| .expect("usable override should resolve"); | ||
|
|
||
| assert_eq!(resolved, Some(override_path)); | ||
| } | ||
|
|
||
| /// Upstream `unusable env override fails without ambient fallback` | ||
| /// (arguments: a missing path, `""`, `" "` and the bare name `agy`), plus | ||
| /// a directory as the Windows form of "not executable". Discovery (PATH and | ||
| /// install directories) must never run, so it panics here. | ||
| #[test] | ||
| fn unusable_override_fails_without_automatic_discovery() { | ||
| let temp = tempfile::tempdir().expect("temporary directory"); | ||
| let overrides = [ | ||
| temp.path().join("missing-agy.exe"), | ||
| PathBuf::from(""), | ||
| PathBuf::from(" "), | ||
| PathBuf::from("agy"), | ||
| temp.path().to_path_buf(), | ||
| ]; | ||
|
|
||
| for value in overrides { | ||
| let error = resolve_agy_binary(Some(value.clone()), || { | ||
| panic!("an unusable configured override must block automatic discovery") | ||
| }) | ||
| .expect_err("unusable override should fail closed"); | ||
|
|
||
| assert!( | ||
| matches!(error, ProviderError::NotInstalled(_)), | ||
| "{value:?}: {error:?}" | ||
| ); | ||
| let message = error.to_string(); | ||
| assert!(message.contains("ANTIGRAVITY_CLI_PATH is set"), "{value:?}"); | ||
| assert!( | ||
| message.contains("automatic CLI discovery is disabled"), | ||
| "{value:?}" | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| #[test] | ||
| fn unset_override_preserves_automatic_discovery() { | ||
| let temp = tempfile::tempdir().expect("temporary directory"); | ||
| let discovered_path = temp.path().join("discovered-agy.exe"); | ||
| std::fs::write(&discovered_path, b"test executable placeholder").expect("write fixture"); | ||
|
|
||
| let resolved = resolve_agy_binary(None, || { | ||
| vec![ | ||
| temp.path().join("missing-first.exe"), | ||
| discovered_path.clone(), | ||
| ] | ||
| }) | ||
| .expect("automatic discovery should resolve"); | ||
|
|
||
| assert_eq!(resolved, Some(discovered_path)); | ||
| } | ||
|
|
||
| /// Upstream `minimal fallback preserves a hit or no executable`: the last | ||
| /// candidate still resolves (above); with no installed CLI the source is | ||
| /// simply missing, which is not an error. | ||
| #[test] | ||
| fn unset_override_without_an_installed_cli_resolves_to_none() { | ||
| let temp = tempfile::tempdir().expect("temporary directory"); | ||
|
|
||
| let resolved = resolve_agy_binary(None, || { | ||
| vec![ | ||
| temp.path().join("missing-first.exe"), | ||
| temp.path().join("missing-last.exe"), | ||
| ] | ||
| }) | ||
| .expect("discovery without a hit is not an error"); | ||
|
|
||
| assert_eq!(resolved, None); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: nesszer/Win-CodexBar
Length of output: 6652
🏁 Script executed:
Repository: nesszer/Win-CodexBar
Length of output: 38154
🌐 Web query:
official Rust std::process::Command current_dir relative program path platform behavior💡 Result:
🏁 Script executed:
Repository: nesszer/Win-CodexBar
Length of output: 15698
🏁 Script executed:
Repository: nesszer/Win-CodexBar
Length of output: 10565
Resolve a configured relative path before returning it.
If
ANTIGRAVITY_CLI_PATHis a bare filename in the current directory,is_file()accepts that file, but the structured fallback launches it with a private working directory. The managed Windows launcher also passes the relative name toCreateProcessWwith a separatecwd. Relative executable lookup is platform-specific, so launch can resolve a differentagyfromPATHor fail. Convert the override to an absolute path before returning it.Suggested fix
if path.is_file() { - Ok(Some(path)) + Ok(Some(path.canonicalize().map_err(|error| { + ProviderError::NotInstalled(format!( + "ANTIGRAVITY_CLI_PATH could not be resolved: {error}" + )) + })?)) } else {🤖 Prompt for AI Agents