agents: drop root, run as per-agent unix user with passwordless sudo (#658)

This commit is contained in:
damocles 2026-05-30 21:14:46 +02:00 committed by Mara
commit 6b6c6775ee
10 changed files with 349 additions and 71 deletions

View file

@ -47,8 +47,9 @@ const RATE_LIMIT_MARKERS: &[&str] = &[
];
/// Substrings that indicate the Anthropic API rejected the request as
/// unauthenticated — the OAuth session in `/root/.claude/` has expired
/// or been revoked. Surfaced as `TurnOutcome::AuthFailed`, which the
/// unauthenticated — the OAuth session in `$HOME/.claude/` (post-#658
/// `/home/<agent>/.claude`, previously `/root/.claude`) has expired or
/// been revoked. Surfaced as `TurnOutcome::AuthFailed`, which the
/// harness uses to flip the container into `needs_login_idle` so the
/// dashboard's re-auth flow takes over (closes #419). Matched against
/// both stdout JSON `error` events and stderr; the markers come from
@ -163,9 +164,13 @@ pub async fn write_settings(socket: &Path) -> Result<PathBuf> {
// same socket-adjacent location every time and so a future override
// (per-agent settings JSON layer) drops in cleanly.
let src = hive_sh4re::assets::claude_settings();
tokio::fs::copy(&src, &path)
.await
.with_context(|| format!("copy claude settings from {} to {}", src.display(), path.display()))?;
tokio::fs::copy(&src, &path).await.with_context(|| {
format!(
"copy claude settings from {} to {}",
src.display(),
path.display()
)
})?;
tracing::info!(path = %path.display(), "wrote claude settings");
Ok(path)
}
@ -919,7 +924,10 @@ mod tests {
// file_count=1 + no mtime, then writing a second file.
let dir = tempfile::tempdir().unwrap();
fs::write(dir.path().join("a"), b"{}").unwrap();
let forged = DirSnapshot { file_count: 1, newest_mtime: None };
let forged = DirSnapshot {
file_count: 1,
newest_mtime: None,
};
fs::write(dir.path().join("b"), b"{}").unwrap();
// Real snapshot has file_count=2, so refresh fires even
// though the mtime axis would be inconclusive.