use std::path::{Path, PathBuf};
use std::sync::OnceLock;
use crate::Workspace;
use anyhow::Result;
static TEMP_ROOT: OnceLock<PathBuf> = OnceLock::new();
static LEGACY_TEMP_DIR: OnceLock<PathBuf> = OnceLock::new();
#[must_use]
pub(crate) fn temp_root() -> Option<&'static Path> {
TEMP_ROOT.get().map(PathBuf::as_path)
}
#[must_use]
pub(crate) fn legacy_temp_dir() -> Option<&'static Path> {
LEGACY_TEMP_DIR.get().map(PathBuf::as_path)
}
#[cfg(unix)]
pub fn init_temp_root() -> anyhow::Result<()> {
let legacy = std::env::temp_dir();
let uid = unsafe { libc::geteuid() };
let root = PathBuf::from("/tmp/mahbot");
match std::fs::create_dir(&root) {
Ok(()) => {
std::fs::set_permissions(&root, std::os::unix::fs::PermissionsExt::from_mode(0o700))
.map_err(|e| {
anyhow::anyhow!("temp root {}: chmod 0700 failed: {e}", root.display())
})?;
}
Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {
use std::os::unix::fs::MetadataExt;
let meta = std::fs::symlink_metadata(&root)
.map_err(|e| anyhow::anyhow!("temp root {}: stat failed: {e}", root.display()))?;
if !meta.is_dir() {
anyhow::bail!(
"temp root {} exists and is not a directory — refusing to use it",
root.display()
);
}
if meta.uid() != uid {
anyhow::bail!(
"temp root {} is owned by uid {} (expected {uid}) — refusing a squatted path",
root.display(),
meta.uid()
);
}
let mode = meta.mode() & 0o777;
if mode & 0o077 != 0 {
std::fs::set_permissions(&root, std::os::unix::fs::PermissionsExt::from_mode(0o700))
.map_err(|e| {
anyhow::anyhow!(
"temp root {} has group/other permissions (mode {mode:o}) and re-chmod 0700 failed: {e}",
root.display()
)
})?;
tracing::warn!(
root = %root.display(),
mode = format_args!("{mode:o}"),
"Temp root had loose permissions — re-chmod 0700 (self-heal, path owned by self)"
);
}
}
Err(e) => anyhow::bail!("temp root {}: create failed: {e}", root.display()),
}
let _ = TEMP_ROOT.set(root.clone());
let _ = LEGACY_TEMP_DIR.set(legacy);
unsafe { std::env::set_var("TMPDIR", &root) };
tracing::info!(root = %root.display(), "Pinned daemon temp root");
Ok(())
}
#[cfg(not(unix))]
pub fn init_temp_root() -> anyhow::Result<()> {
Ok(())
}
#[must_use]
pub(crate) fn shell_tmpdir() -> String {
temp_root().map_or_else(|| "/tmp".to_string(), |p| p.to_string_lossy().into_owned())
}
#[must_use]
pub(crate) fn bare_mktemp_landing_root() -> PathBuf {
#[cfg(target_os = "macos")]
{
if let Some(legacy) = legacy_temp_dir() {
return legacy.to_path_buf();
}
}
std::env::temp_dir()
}
const TEMP_CLEANUP_PROMPT_KEY: &str = "sanitation/temp_cleanup.md";
const TEMP_CLEANUP_WORKSPACE_NAME: &str = "tmp";
pub(crate) async fn temp_cleanup_row_exists(conn: &crate::db::Connection) -> Result<bool> {
Ok(conn
.query_optional(
"SELECT 1 FROM jobs WHERE kind = 'temp_cleanup' AND status != 'done' LIMIT 1",
(),
|_| Ok::<(), anyhow::Error>(()),
)
.await?
.is_some())
}
pub(crate) async fn dispatch_temp_cleanup() -> Result<()> {
let conn = &crate::session::store().conn;
if temp_cleanup_row_exists(conn).await? {
tracing::info!("Temp-dir cleanup already in flight — skipping dispatch");
return Ok(());
}
let job_id = crate::generate_id();
let ws = Workspace::ephemeral_run(TEMP_CLEANUP_WORKSPACE_NAME, Path::new("/tmp"));
let prompt = crate::prompt::load_prompt(TEMP_CLEANUP_PROMPT_KEY);
crate::jobs::spawn_job(
conn,
&job_id,
&prompt,
&ws.name,
"",
"",
crate::Role::Sanitation,
&[crate::jobs::NewAgent {
agent_id: crate::research_cleanup::cleanup_agent_id(&job_id),
kind: crate::jobs::AgentKind::Sanitation,
idx: None,
task: prompt.clone(),
}],
&crate::jobs::SpawnChild::TempCleanup,
)
.await
.map_err(|e| {
tracing::error!(job = %job_id, error = %e, "Failed to spawn temp-dir cleanup job");
e
})?;
let ws = ws.clone();
let job_id_log = job_id.clone();
let agent_id = crate::research_cleanup::cleanup_agent_id(&job_id);
let agent_id_log = agent_id.clone();
tokio::spawn(async move {
run_temp_cleanup_and_finish(&job_id, &ws, &prompt).await;
});
tracing::info!(job = %job_id_log, agent = %agent_id_log, "Temp-dir cleanup dispatched");
Ok(())
}
async fn run_temp_cleanup_and_finish(job_id: &str, ws: &Workspace, prompt: &str) {
let agent_id = crate::research_cleanup::cleanup_agent_id(job_id);
let (agent, response) = crate::agent::run_default_agent(
&agent_id,
crate::Role::Sanitation,
ws,
prompt,
None,
None,
None,
)
.await;
let report = response.unwrap_or_else(|| {
format!(
"Temp-dir cleanup FAILED (job {job_id}): {}",
agent
.failure
.clone()
.unwrap_or_else(|| "no failure detail".to_string())
)
});
tracing::info!(
job = %job_id,
agent = %agent_id,
"Temp-dir cleanup finished: {}",
crate::util::scrub_credentials(&report)
);
let _ = crate::jobs::terminalize_job(&crate::session::store().conn, job_id).await;
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(unix)]
#[test]
fn legacy_capture_and_pin_are_consistent() {
assert!(temp_root().is_none());
assert!(legacy_temp_dir().is_none());
assert_eq!(shell_tmpdir(), "/tmp");
}
async fn init_stores() {
crate::util::test::init_management_test_stores().await;
}
#[tokio::test]
#[serial_test::serial(reset_inflight)] async fn dispatch_temp_cleanup_deduped_by_jobs_row() {
init_stores().await;
crate::jobs::spawn_job(
&crate::session::store().conn,
"tmpclean_dedup",
"task",
"tmp",
"",
"",
crate::Role::Sanitation,
&[],
&crate::jobs::SpawnChild::TempCleanup,
)
.await
.unwrap();
assert!(
temp_cleanup_row_exists(&crate::session::store().conn)
.await
.unwrap(),
"the pre-created row is the dedup marker"
);
dispatch_temp_cleanup().await.unwrap();
let rows = crate::session::store()
.conn
.query("SELECT COUNT(*) FROM jobs WHERE kind = 'temp_cleanup'", ())
.await
.unwrap();
assert_eq!(
rows[0].get::<i64>(0).unwrap(),
1,
"single temp_cleanup job row"
);
let sessions = crate::session::store()
.conn
.query(
"SELECT COUNT(*) FROM session_metadata WHERE agent_id = 'cleanup_tmpclean_dedup'",
(),
)
.await
.unwrap();
assert_eq!(
sessions[0].get::<i64>(0).unwrap(),
0,
"deduped dispatch must not spawn the temp cleaner"
);
crate::jobs::terminalize_job(&crate::session::store().conn, "tmpclean_dedup")
.await
.unwrap();
}
}