hive-ag3nt: scrub turn.rs cookies (#716 batch 7)
12 → 0 cookies in turn.rs (claude --print pump + stream-json + compaction + wait_for_login). Cookie scrub only — substantive prose (auth-failed detection, mtime-snapshot resumption, DirSnapshot two-axis design, AuthFailed turn outcome) is already documented in docs/turn-loop.md::The loop and the Turn outcomes table. Rustdocs trimmed to point at the doc where useful (AUTH_FAIL_MARKERS, wait_for_login). - AUTH_FAIL_MARKERS doc: drop #658, #419 attribution - write_settings inner comment: drop #555 attribution - write_system_prompt doc: drop #519 attribution - TurnOutcome::AuthFailed doc: drop #419 attribution - wait_for_login doc: drop #542 attribution, point at docs - wait_for_login inner emit_status comment: drop #563 attribution - Test comments: drop #542 and argus #545 attribution
This commit is contained in:
parent
55716be8fc
commit
f8e0061f3f
1 changed files with 32 additions and 39 deletions
|
|
@ -20,7 +20,7 @@ use crate::mcp;
|
|||
|
||||
// `--settings` JSON is read at runtime from
|
||||
// `$HIVE_ASSETS_DIR/prompts/claude-settings.json` via
|
||||
// `hive_sh4re::assets::claude_settings()` (#555). We turn off claude's
|
||||
// `hive_sh4re::assets::claude_settings()`. We turn off claude's
|
||||
// in-session auto-compaction and its cross-session auto-memory because
|
||||
// hyperhive owns those concerns (`/compact` on overflow, notes
|
||||
// persistence under `/state`). Unknown keys are silently ignored by
|
||||
|
|
@ -47,15 +47,16 @@ const RATE_LIMIT_MARKERS: &[&str] = &[
|
|||
];
|
||||
|
||||
/// Substrings that indicate the Anthropic API rejected the request as
|
||||
/// 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
|
||||
/// unauthenticated — the OAuth session in `$HOME/.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
|
||||
/// claude-code's `api_retry` events (`{"error":"authentication_failed",
|
||||
/// dashboard's re-auth flow takes over. Matched against both stdout
|
||||
/// JSON `error` events and stderr; the markers come from claude-code's
|
||||
/// `api_retry` events (`{"error":"authentication_failed",
|
||||
/// "error_status":401,...}`) and the human-readable
|
||||
/// "Failed to authenticate. API Error: 401" line claude prints on giveup.
|
||||
/// See [`docs/turn-loop.md::The loop`](../../docs/turn-loop.md) for the
|
||||
/// re-auth resumption path.
|
||||
const AUTH_FAIL_MARKERS: &[&str] = &[
|
||||
"\"error\":\"authentication_failed\"",
|
||||
"\"error_status\":401",
|
||||
|
|
@ -159,7 +160,7 @@ pub async fn write_settings(_socket: &Path) -> Result<PathBuf> {
|
|||
let parent = crate::paths::config_dir();
|
||||
tokio::fs::create_dir_all(&parent).await.ok();
|
||||
let path = parent.join("claude-settings.json");
|
||||
// #555: source-of-truth is `$HIVE_ASSETS_DIR/prompts/claude-settings.json`;
|
||||
// Source-of-truth is `$HIVE_ASSETS_DIR/prompts/claude-settings.json`;
|
||||
// copy through the per-agent runtime dir so claude reads it from the
|
||||
// same socket-adjacent location every time and so a future override
|
||||
// (per-agent settings JSON layer) drops in cleanly.
|
||||
|
|
@ -177,8 +178,8 @@ pub async fn write_settings(_socket: &Path) -> Result<PathBuf> {
|
|||
|
||||
/// Thin re-export of [`crate::prompt::write_system_prompt`] for
|
||||
/// callers that already import this module. The actual rendering +
|
||||
/// marker-block logic lives in `prompt.rs` (closes #519); this is
|
||||
/// just the public entry point the binaries call.
|
||||
/// marker-block logic lives in `prompt.rs`; this is just the public
|
||||
/// entry point the binaries call.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
|
|
@ -211,7 +212,7 @@ pub enum TurnOutcome {
|
|||
/// The Anthropic API rejected the request with 401 (OAuth session
|
||||
/// expired or revoked). The serve loop should flip the container
|
||||
/// into `needs_login_idle` and stop driving turns until the
|
||||
/// operator re-auths via the per-agent web UI (closes #419).
|
||||
/// operator re-auths via the per-agent web UI.
|
||||
AuthFailed,
|
||||
Failed(anyhow::Error),
|
||||
}
|
||||
|
|
@ -477,17 +478,12 @@ pub fn emit_turn_end(bus: &Bus, outcome: &TurnOutcome) {
|
|||
/// Block until the bound `~/.claude/` dir contains a session that
|
||||
/// post-dates this call, polling on a `poll_ms` interval (min 2s).
|
||||
/// Flips `state` to `Online` when login lands; caller resumes its
|
||||
/// serve loop.
|
||||
///
|
||||
/// **Mtime-progress, not bare existence (closes #542)**: an
|
||||
/// existence-only check (the pre-#542 behaviour) immediately returns
|
||||
/// after a 401 because the stale `credentials.json` is still on disk
|
||||
/// — the next turn then 401s on the same tokens and the harness
|
||||
/// loops forever. We snapshot the newest file mtime in `claude_dir`
|
||||
/// at entry and only resume when something has been written since
|
||||
/// that snapshot (the operator's `/login/code` flow lands a refreshed
|
||||
/// credentials file, bumping its mtime). First-time login (empty
|
||||
/// dir → `None` snapshot) still flips on the first file appearing.
|
||||
/// serve loop. Snapshots the dir at entry and only resumes when the
|
||||
/// snapshot advances (mtime OR file-count change), avoiding the
|
||||
/// infinite-401 loop a bare-existence check would produce when stale
|
||||
/// credentials are already on disk. Mtime-snapshot resumption rationale
|
||||
/// + DirSnapshot two-axis design: see
|
||||
/// [`docs/turn-loop.md::The loop`](../../docs/turn-loop.md).
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
|
|
@ -502,16 +498,13 @@ pub async fn wait_for_login(
|
|||
claude_dir = %claude_dir.display(),
|
||||
"no claude session — staying in partial-run mode (web UI only)"
|
||||
);
|
||||
// #563: announce `needs_login_idle` to the bus so the sentinel file
|
||||
// Announce `needs_login_idle` to the bus so the sentinel file
|
||||
// (`{state_dir}/hyperhive-needs-login`) gets written on every entry
|
||||
// path — cold-boot, 401-mid-turn, and `/api/logout`. The host's
|
||||
// `auth_failed_sentinel` reads that file to surface `needs_login` on
|
||||
// the dashboard; prior to this call the cold-boot + 401 paths flipped
|
||||
// `LoginState::NeedsLogin` in memory but never touched the sentinel,
|
||||
// so the dashboard kept rendering `online` for a parked harness.
|
||||
// Idempotent — `emit_status` is a `write` on a small empty file, so
|
||||
// re-entering this function after a transient operator action is a
|
||||
// no-op for the on-disk state.
|
||||
// `auth_failed_sentinel` reads that file to surface `needs_login`
|
||||
// on the dashboard. Idempotent — `emit_status` is a `write` on a
|
||||
// small empty file, so re-entering this function after a transient
|
||||
// operator action is a no-op for the on-disk state.
|
||||
bus.emit_status("needs_login_idle");
|
||||
let snapshot = snapshot_dir(claude_dir);
|
||||
let probe = Duration::from_millis(poll_ms.max(2000));
|
||||
|
|
@ -867,7 +860,7 @@ mod tests {
|
|||
#[test]
|
||||
fn session_refreshed_first_login_flips_on_any_file() {
|
||||
// Empty-dir snapshot → any file appearing means a fresh
|
||||
// login landed. Pre-#542 semantics for first-time login.
|
||||
// login landed. First-time login semantics.
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let snapshot = snapshot_dir(dir.path());
|
||||
assert!(!session_refreshed(snapshot, snapshot_dir(dir.path())));
|
||||
|
|
@ -877,9 +870,9 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn session_refreshed_stale_creds_dont_flip_immediately() {
|
||||
// The #542 repro: stale credentials.json already exists at
|
||||
// entry; wait_for_login must NOT immediately return — it
|
||||
// would loop straight into another 401-failing turn.
|
||||
// Stale credentials.json already exists at entry; wait_for_login
|
||||
// must NOT immediately return — it would loop straight into
|
||||
// another 401-failing turn.
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
fs::write(dir.path().join("credentials.json"), b"{}").unwrap();
|
||||
let snapshot = snapshot_dir(dir.path());
|
||||
|
|
@ -917,11 +910,11 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn session_refreshed_count_change_flips_when_mtime_unreadable() {
|
||||
// Defensive (argus #545 nit): if all files have unreadable
|
||||
// `meta.modified()` (exotic fs / NFS), newest_mtime stays
|
||||
// `None` forever — but file_count axis still catches new
|
||||
// files appearing. Simulated here by forging a snapshot with
|
||||
// file_count=1 + no mtime, then writing a second file.
|
||||
// Defensive: if all files have unreadable `meta.modified()`
|
||||
// (exotic fs / NFS), newest_mtime stays `None` forever — but
|
||||
// file_count axis still catches new files appearing. Simulated
|
||||
// here by forging a snapshot with 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 {
|
||||
|
|
|
|||
Loading…
Reference in a new issue