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
42 changes: 30 additions & 12 deletions crates/aft/src/bash_background/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,19 +159,37 @@ pub fn storage_dir(configured: Option<&std::path::Path>) -> PathBuf {
if let Some(dir) = std::env::var_os("AFT_CACHE_DIR") {
return PathBuf::from(dir).join("aft");
}
// Fallback to the user's home directory. On Unix this is `$HOME`; on
// Windows `HOME` is typically unset, so fall back to `USERPROFILE`
// (which is always set in interactive sessions and in the env that
// OpenCode/Pi pass through to plugin processes). If both are missing
// (rare — embedded contexts, broken shells), fall back to a temp
// Fallback mirrors resolveCortexKitStorageRoot() from the TS plugin
// (packages/opencode-plugin/src/shared/storage-paths.ts).
if let Some(dir) = std::env::var_os("XDG_DATA_HOME") {
return PathBuf::from(dir).join("cortexkit").join("aft");
}
if cfg!(target_os = "windows") {
if let Some(dir) = std::env::var_os("LOCALAPPDATA")
.or_else(|| std::env::var_os("APPDATA"))
{
return PathBuf::from(dir).join("cortexkit").join("aft");
}
}
// On Unix `$HOME`; on Windows `HOME` is typically unset, so fall back to
// `USERPROFILE` (which is always set in interactive sessions and in the
// env that OpenCode/Pi pass through to plugin processes). If both are
// missing (rare — embedded contexts, broken shells), fall back to a temp
// directory rather than `"."` — a relative path makes bg-bash wrapper
// commands like `move /Y .\.cache\aft\... ...` fail with "system
// cannot find the path specified" once the working directory shifts.
let home = std::env::var_os("HOME")
.or_else(|| std::env::var_os("USERPROFILE"))
.map(PathBuf::from)
.unwrap_or_else(std::env::temp_dir);
home.join(".cache").join("aft")
// commands like `move /Y .\.cache\aft\... ...` fail with "system cannot
// find the path specified" once the working directory shifts.
let home = if cfg!(target_os = "windows") {
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
std::env::var_os("USERPROFILE").or_else(|| std::env::var_os("HOME"))
} else {
std::env::var_os("HOME")
}
.map(PathBuf::from)
.unwrap_or_else(std::env::temp_dir);
if cfg!(target_os = "windows") {
home.join("AppData").join("Local").join("cortexkit").join("aft")
} else {
home.join(".local").join("share").join("cortexkit").join("aft")
}
}

pub fn repair_legacy_root_tasks(storage_root: &std::path::Path, harness: crate::harness::Harness) {
Expand Down
84 changes: 79 additions & 5 deletions crates/aft/src/cli/warmup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ pub fn run(args: Vec<OsString>) -> Result<(), WarmupError> {
);
}

// Resolve ONNX Runtime from the plugin-managed cache so `aft warmup`
// can build the semantic index without the plugin setting ORT_DYLIB_PATH.
if args.areas.semantic {
try_set_ort_dylib_path(&storage_dir);
}

let ctx = AppContext::new(Box::new(TreeSitterProvider::new()), Config::default());
configure(&ctx, &root, &storage_dir, args.areas, args.force)?;

Expand Down Expand Up @@ -248,11 +254,50 @@ fn warmup_storage_dir() -> PathBuf {
if let Some(value) = std::env::var_os("AFT_STORAGE_DIR") {
return PathBuf::from(value);
}
let home = std::env::var_os("HOME")
.or_else(|| std::env::var_os("USERPROFILE"))
.map(PathBuf::from)
.unwrap_or_else(std::env::temp_dir);
home.join(".cache").join("aft")
// Fallback mirrors resolveCortexKitStorageRoot() from the TS plugin.
if let Some(dir) = std::env::var_os("XDG_DATA_HOME") {
return PathBuf::from(dir).join("cortexkit").join("aft");
}
if cfg!(target_os = "windows") {
if let Some(dir) = std::env::var_os("LOCALAPPDATA")
.or_else(|| std::env::var_os("APPDATA"))
{
return PathBuf::from(dir).join("cortexkit").join("aft");
}
}
let home = if cfg!(target_os = "windows") {
std::env::var_os("USERPROFILE").or_else(|| std::env::var_os("HOME"))
} else {
std::env::var_os("HOME")
}
.map(PathBuf::from)
.unwrap_or_else(std::env::temp_dir);
if cfg!(target_os = "windows") {
home.join("AppData").join("Local").join("cortexkit").join("aft")
} else {
home.join(".local").join("share").join("cortexkit").join("aft")
}
}

/// Set `ORT_DYLIB_PATH` from a cached ONNX Runtime library if not already set.
fn try_set_ort_dylib_path(storage_dir: &std::path::Path) {
if std::env::var_os("ORT_DYLIB_PATH").is_some() {
return;
}
let lib_name = if cfg!(target_os = "macos") {
"libonnxruntime.dylib"
} else if cfg!(target_os = "windows") {
"onnxruntime.dll"
} else {
"libonnxruntime.so"
};
let ort_dir = storage_dir.join("onnxruntime").join("1.24.4");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Hardcoded ONNX Runtime version string can drift from the actual cached ORT version and break semantic warmup.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At crates/aft/src/cli/warmup.rs, line 294:

<comment>Hardcoded ONNX Runtime version string can drift from the actual cached ORT version and break semantic warmup.</comment>

<file context>
@@ -248,11 +254,50 @@ fn warmup_storage_dir() -> PathBuf {
+    } else {
+        "libonnxruntime.so"
+    };
+    let ort_dir = storage_dir.join("onnxruntime").join("1.24.4");
+    for candidate in [ort_dir.join(lib_name), ort_dir.join("lib").join(lib_name)] {
+        if candidate.is_file() {
</file context>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pre-existing pattern! The plugin side already pins 1.24.4 in two separate files (onnx-runtime.ts, onnx.ts). The Rust string is a third copy. Cross-language constant sync would require build-time codegen, which is out of scope for this fix.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The version string "1.24.4" is hardcoded in the library probe path. When the plugin upgrades its ONNX Runtime cache (e.g. onnxruntime/1.25.0/), try_set_ort_dylib_path will silently find nothing and ORT_DYLIB_PATH will remain unset. Given the PR description already acknowledges this, consider at least making it a named constant so there is a single place to update.

Suggested change
let ort_dir = storage_dir.join("onnxruntime").join("1.24.4");
const ORT_VERSION: &str = "1.24.4";
let ort_dir = storage_dir.join("onnxruntime").join(ORT_VERSION);

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

for candidate in [ort_dir.join(lib_name), ort_dir.join("lib").join(lib_name)] {
if candidate.is_file() {
std::env::set_var("ORT_DYLIB_PATH", &candidate);
break;
}
}
Comment on lines +294 to +300

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Silent no-op when library not cached

try_set_ort_dylib_path returns without setting ORT_DYLIB_PATH (and without any diagnostic) if neither candidate path exists. The subsequent configure call will then hit the same dlopen failure this fix is meant to prevent, with no indication of why. A user who hasn't yet run the plugin (so the library was never downloaded) will see the exact same opaque error as before. At minimum, an eprintln! warning pointing to the expected path would help with diagnosis.

}

/// Build the `configure` params for a warmup run.
Expand Down Expand Up @@ -863,4 +908,33 @@ mod tests {
assert_eq!(err.exit_code(), 2);
assert!(err.to_string().contains("--only requires a value"));
}

#[test]
fn try_set_ort_dylib_path_finds_cached_library() {
use std::sync::Mutex;
static LOCK: Mutex<()> = Mutex::new(());
let _guard = LOCK.lock().unwrap();
std::env::remove_var("ORT_DYLIB_PATH");

let temp = tempfile::tempdir().unwrap();
let lib_name = if cfg!(target_os = "macos") {
"libonnxruntime.dylib"
} else if cfg!(target_os = "windows") {
"onnxruntime.dll"
} else {
"libonnxruntime.so"
};
let ort_dir = temp.path().join("onnxruntime").join("1.24.4");
std::fs::create_dir_all(&ort_dir).unwrap();
let lib_path = ort_dir.join(lib_name);
std::fs::write(&lib_path, b"fake").unwrap();

try_set_ort_dylib_path(temp.path());

assert_eq!(
std::env::var_os("ORT_DYLIB_PATH"),
Some(lib_path.as_os_str().to_owned())
);
std::env::remove_var("ORT_DYLIB_PATH");
}
}
41 changes: 34 additions & 7 deletions crates/aft/src/search_index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1696,15 +1696,42 @@ pub fn resolve_cache_dir(project_root: &Path, storage_dir: Option<&Path>) -> Pat
if let Some(dir) = storage_dir {
return dir.join("index").join(artifact_cache_key(project_root));
}
// Fallback to ~/.cache/aft/ (legacy, for standalone binary usage).
// On Windows `HOME` is typically unset, so try `USERPROFILE` next.
// Fallback mirrors resolveCortexKitStorageRoot() from the TS plugin
// (packages/opencode-plugin/src/shared/storage-paths.ts).
if let Some(dir) = std::env::var_os("XDG_DATA_HOME") {
return PathBuf::from(dir)
.join("cortexkit")
.join("aft")
.join("index")
.join(artifact_cache_key(project_root));
}
if cfg!(target_os = "windows") {
if let Some(dir) = std::env::var_os("LOCALAPPDATA")
.or_else(|| std::env::var_os("APPDATA"))
{
return PathBuf::from(dir)
.join("cortexkit")
.join("aft")
.join("index")
.join(artifact_cache_key(project_root));
}
}
// On Windows prefer `USERPROFILE`; fall back to `HOME`.
// If neither is set, fall back to a temp directory rather than `"."`
// because the search-index code reads/writes absolute paths.
let home = std::env::var_os("HOME")
.or_else(|| std::env::var_os("USERPROFILE"))
.map(PathBuf::from)
.unwrap_or_else(std::env::temp_dir);
home.join(".cache")
let home = if cfg!(target_os = "windows") {
std::env::var_os("USERPROFILE").or_else(|| std::env::var_os("HOME"))
} else {
std::env::var_os("HOME")
}
.map(PathBuf::from)
.unwrap_or_else(std::env::temp_dir);
let root = if cfg!(target_os = "windows") {
home.join("AppData").join("Local")
} else {
home.join(".local").join("share")
};
root.join("cortexkit")
.join("aft")
.join("index")
.join(artifact_cache_key(project_root))
Expand Down