Project
cortex
Description
In cortex-app-server/src/api/agents.rs, the following functions are declared as async handlers but use synchronous (blocking) filesystem operations:
pub async fn list_agents() -> AppResult<...> {
// project agents
let entries = std::fs::read_dir(project_dir); // BUG: BLOCKING!
for entry in entries.flatten() {
read_agent_file(&entry.path(), 'project');
}
// user agents
let entries = std::fs::read_dir(&user_dir); // BUG: BLOCKING!
}
fn read_agent_file(path: &Path, scope: &str) -> Option {
let content = std::fs::read_to_string(path).ok()?; // BUG: BLOCKING!
let name = path.file_stem()?.to_str()?.to_string();
// ...
}
These synchronous calls (std::fs::read_dir, std::fs::read_to_string) block the Tokio runtime thread for the duration of the I/O operation. Under load or with slow storage (NFS, networked filesystems, large agent directories), this can stall all other async tasks running on the same thread, including WebSocket event processing, AI session management, and live terminal output.
The same blocking pattern appears in get_agent() (std::fs calls via read_agent_file), create_agent() (std::fs::write), delete_agent() (std::fs::remove_file), and update_agent() (std::fs::write).
Error Message
Debug Logs
cortex-app-server/src/api/agents.rs:74-103 (list_agents — blocking I/O):
pub async fn list_agents() -> AppResult<Json<Vec<AgentDefinition>>> {
let entries = std::fs::read_dir(project_dir); // BLOCKING!
for entry in entries.flatten() {
read_agent_file(&entry.path(), 'project'); // BLOCKING: read_to_string!
}
let entries = std::fs::read_dir(&user_dir); // BLOCKING!
...
}
cortex-app-server/src/api/agents.rs:24 (read_agent_file — blocking read):
let content = std::fs::read_to_string(path).ok()?; // BLOCKING!
cortex-app-server/src/api/agents.rs:156 (create_agent — blocking write):
std::fs::write(&path, &content) // BLOCKING!
cortex-app-server/src/api/agents.rs:175 (delete_agent — blocking remove):
std::fs::remove_file(&project_path) // BLOCKING!
warning: Tokio runtime blocked for filesystem I/O in list_agents()
warning: concurrent AI sessions stalled while listing agent files
System Information
OS: Ubuntu 22.04 LTS | Version: v0.0.7
Screenshots
https://github.com/petar-vasilev/screenshots/blob/main/5adcb10e198e4d2da38cfcd1c8655a7e.png
Steps to Reproduce
- Run:
curl http://localhost:8080/api/agents
- Start cortex app-server with multiple active AI sessions
- Send many concurrent GET /api/agents requests
- Each request calls std::fs::read_dir() + std::fs::read_to_string() synchronously
- The Tokio thread is blocked for each I/O operation
- Active AI sessions experience delayed WebSocket events and processing stalls
- Under slow filesystem (NFS/CIFS), a single list_agents call can stall Tokio for seconds
Expected Behavior
All filesystem operations in async handlers should use tokio::fs equivalents:
// Correct async approach:
pub async fn list_agents() -> AppResult<...> {
let entries = tokio::fs::read_dir(project_dir).await?; // async!
while let Ok(Some(entry)) = entries.next_entry().await {
let content = tokio::fs::read_to_string(entry.path()).await?; // async!
}
}
Alternatively, use tokio::task::spawn_blocking() to run blocking I/O on a dedicated thread pool without blocking the async runtime.
Actual Behavior
list_agents(), read_agent_file(), create_agent(), delete_agent(), and update_agent() all use std::fs blocking calls (read_dir, read_to_string, write, remove_file) inside async handler functions. This blocks the Tokio runtime thread, degrading performance and potentially causing DoS under concurrent load or slow filesystem conditions.
Additional Context
── Code Evidence ────────────────────────────────────────────────────
This is the same blocking-I/O-in-async pattern as the already-reported bugs in api/git.rs (blocking std::process::Command for all git operations) and api/search.rs (blocking std::process::Command for ripgrep).
The full list of async agent handlers using blocking std::fs calls:
- list_agents(): std::fs::read_dir x2, std::fs::read_to_string via read_agent_file
- get_agent(): std::fs::read_to_string via read_agent_file
- create_agent(): std::fs::create_dir_all, std::fs::write
- delete_agent(): std::fs::remove_file
- update_agent(): std::fs::read_to_string, std::fs::write
Project
cortex
Description
In cortex-app-server/src/api/agents.rs, the following functions are declared as async handlers but use synchronous (blocking) filesystem operations:
pub async fn list_agents() -> AppResult<...> {
// project agents
let entries = std::fs::read_dir(project_dir); // BUG: BLOCKING!
for entry in entries.flatten() {
read_agent_file(&entry.path(), 'project');
}
// user agents
let entries = std::fs::read_dir(&user_dir); // BUG: BLOCKING!
}
fn read_agent_file(path: &Path, scope: &str) -> Option {
let content = std::fs::read_to_string(path).ok()?; // BUG: BLOCKING!
let name = path.file_stem()?.to_str()?.to_string();
// ...
}
These synchronous calls (std::fs::read_dir, std::fs::read_to_string) block the Tokio runtime thread for the duration of the I/O operation. Under load or with slow storage (NFS, networked filesystems, large agent directories), this can stall all other async tasks running on the same thread, including WebSocket event processing, AI session management, and live terminal output.
The same blocking pattern appears in get_agent() (std::fs calls via read_agent_file), create_agent() (std::fs::write), delete_agent() (std::fs::remove_file), and update_agent() (std::fs::write).
Error Message
Debug Logs
cortex-app-server/src/api/agents.rs:74-103 (list_agents — blocking I/O): pub async fn list_agents() -> AppResult<Json<Vec<AgentDefinition>>> { let entries = std::fs::read_dir(project_dir); // BLOCKING! for entry in entries.flatten() { read_agent_file(&entry.path(), 'project'); // BLOCKING: read_to_string! } let entries = std::fs::read_dir(&user_dir); // BLOCKING! ... } cortex-app-server/src/api/agents.rs:24 (read_agent_file — blocking read): let content = std::fs::read_to_string(path).ok()?; // BLOCKING! cortex-app-server/src/api/agents.rs:156 (create_agent — blocking write): std::fs::write(&path, &content) // BLOCKING! cortex-app-server/src/api/agents.rs:175 (delete_agent — blocking remove): std::fs::remove_file(&project_path) // BLOCKING! warning: Tokio runtime blocked for filesystem I/O in list_agents() warning: concurrent AI sessions stalled while listing agent filesSystem Information
OS: Ubuntu 22.04 LTS | Version: v0.0.7Screenshots
https://github.com/petar-vasilev/screenshots/blob/main/5adcb10e198e4d2da38cfcd1c8655a7e.png
Steps to Reproduce
curl http://localhost:8080/api/agentsExpected Behavior
All filesystem operations in async handlers should use tokio::fs equivalents:
// Correct async approach:
pub async fn list_agents() -> AppResult<...> {
let entries = tokio::fs::read_dir(project_dir).await?; // async!
while let Ok(Some(entry)) = entries.next_entry().await {
let content = tokio::fs::read_to_string(entry.path()).await?; // async!
}
}
Alternatively, use tokio::task::spawn_blocking() to run blocking I/O on a dedicated thread pool without blocking the async runtime.
Actual Behavior
list_agents(), read_agent_file(), create_agent(), delete_agent(), and update_agent() all use std::fs blocking calls (read_dir, read_to_string, write, remove_file) inside async handler functions. This blocks the Tokio runtime thread, degrading performance and potentially causing DoS under concurrent load or slow filesystem conditions.
Additional Context
── Code Evidence ────────────────────────────────────────────────────
This is the same blocking-I/O-in-async pattern as the already-reported bugs in api/git.rs (blocking std::process::Command for all git operations) and api/search.rs (blocking std::process::Command for ripgrep).
The full list of async agent handlers using blocking std::fs calls:
- list_agents(): std::fs::read_dir x2, std::fs::read_to_string via read_agent_file
- get_agent(): std::fs::read_to_string via read_agent_file
- create_agent(): std::fs::create_dir_all, std::fs::write
- delete_agent(): std::fs::remove_file
- update_agent(): std::fs::read_to_string, std::fs::write