Skip to content

0.1.4: transient auditor-guard snapshot failure (filesystem race) aborts the whole run as provider_provider_error #27

Description

@TON14

Summary

On 0.1.4, a transient filesystem race during the auditor read-only-guard snapshot aborts the entire run with provider_provider_error, even though the agent runtime itself is healthy. On 0.1.3 the same situation only failed the current round, which was then retried.

What happens

The auditor guard takes a workspace manifest before and after the auditor episode (snapshot_workspace in src/lh_harness/adapters/claude_permissions.py). If the workspace contains a directory that changes while the walk is in progress — the typical case is a build/cache directory such as a Rust target/ being rewritten by an incremental build — the walk records an OSError for the affected entries, e.g.:

OSError: [Errno 9] Bad file descriptor

The adapter then rejects the audit fail-closed, which is reasonable:

Auditor workspace read-only guard could not inspect every path; the audit was rejected fail-closed.

But because the episode result now carries status="error", the classifier introduced in 0.1.4 (classify_agent_runtime_failure in src/lh_harness/provider_errors.py) treats it as a terminal agent-runtime failure. None of the specific patterns (auth, quota, rate limit, network, model) match, so it falls through to the generic provider_error kind and the run is aborted with abort_reason=provider_provider_error.

So a local, transient audit-validity problem — the guard could not take a clean snapshot — is escalated into a fatal provider failure for the whole run. In a long multi-hour run this is expensive: everything stops over a filesystem race that would likely not recur on the next round.

Reproduction

Observed twice in real multi-hour runs (agent claude_code, Linux) on a Rust workspace, where the executor's cargo builds keep target/ churning and a snapshot walk overlapping such churn eventually hits the race.

A deterministic reproduction of the escalation (without waiting for the race itself):

  1. Take any EpisodeResult whose metadata contains a non-empty verifier_workspace_snapshot_errors list and whose error ends with the fail-closed guard message above (this is exactly what ClaudeCodeAdapter.run_episode produces when the snapshot diff reports errors).
  2. Pass it to classify_agent_runtime_failure.
  3. It returns an AgentRuntimeFailure with abort_reason="provider_provider_error" — which the run loop treats as terminal — instead of leaving the failure to the normal round-retry path.

To provoke the underlying race itself: run with an auditor role on a workspace where a large directory is concurrently being rewritten during the auditor episode (e.g. a background loop that keeps creating/removing many files in a subdirectory while the audit runs). The before/after snapshot_workspace walk over that directory eventually returns verifier_workspace_snapshot_errors.

Expected behavior

The fail-closed rejection of the audit is fine, but its blast radius should be the round (fail + retry, as on 0.1.3), not the run. A guard snapshot failure is not an agent-runtime/provider failure.

Environment

  • lh-harness 0.1.4 (pip/uv install)
  • Linux x86_64
  • agent: claude_code, auditor role with the read-only guard active

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions