Skip to content

Reopened producer preparation replays dependency patches over stale task-local workspace #949

Closed

Description

@aviggiano

Summary

When resume --retry-failed reopens an agent producer and its generated prepare:<attempt> task, the durable task worktree may still contain source files authored by the producer's earlier execution. Initial preparation calls materializeWorkspacePatchDependencies() before restoring the persisted workspace-patch preparation tree, so the first dependency patch sees task-local stale source and rejects it as a base-tree mismatch.

Preparation has a retry budget, but retrying is deterministic: the preparation task body does not reset the worktree between attempts. The restore currently occurs only inside resetTaskArtifactsForRetry(), which is reached from the agent's generate() method after preparation succeeds. A failure in preparation can never reach that restore.

Generic reproduction

  1. Execute a generated task whose dependency chain publishes a workspace patch.
  2. Let the agent author a task-local source file and finish, then fail its zero-retry artifact verifier.
  3. Resume with --retry-failed so the producer closure is reopened.
  4. Observe the reopened prepare:<attempt> task fail while applying the first dependency patch because the durable worktree contains both the dependency state and the task's earlier source output.
  5. Observe the automatic preparation retry fail identically because no reset runs between attempts.

A sibling whose prior execution authored artifacts only, without changing captured source roots, prepares successfully. This distinguishes task-local stale workspace state from dependency artifact corruption or a transient Git race.

Root cause

The generated workflow's ordering is:

prepareArtifactMirror
  -> materializeWorkspacePatchDependencies
  -> restoreInvariantSuiteWorkspaceSnapshot

agent generate
  -> resetTaskArtifactsForRetry
     -> restoreWorkspacePatchPreparation

The durable workspace-patch-preparation.json already records the authenticated pre-agent tree needed for recovery, but the reopened preparation path reads it only after attempting dependency replay. The recovery primitive therefore exists but is ordered behind the failure.

Relationship to existing fixes

This is distinct from #312 / #314. That fix safely skips an authenticated linear dependency prefix when the current tree exactly equals a dependency result_tree. Here, the current tree includes the task's own prior output and intentionally matches no dependency result, so the strict mismatch remains correct.

It is also the stateful-retry test gap called out during review of #829: the retry test proved Smithers invokes preparation twice, but did not exercise the real preparation sequence after durable workspace mutation.

Expected behavior

When authenticated persisted preparation evidence exists for a reopened producer, initial preparation must restore that exact tree before replaying dependency patches. A genuinely fresh task with no persisted evidence must retain the current strict dependency replay behavior. The automatic preparation retry must begin from the same authenticated preparation state rather than the partially mutated or task-authored worktree.

Acceptance criteria

  • A reopened producer whose durable worktree contains task-local source drift restores its persisted preparation tree before dependency replay.
  • Its prepare:<attempt> task succeeds without weakening applyWorkspacePatch base-tree validation.
  • A fresh task with no preparation evidence still replays dependency patches from the pinned baseline.
  • Missing, malformed, modified, or unauthenticated preparation evidence still fails closed.
  • A preparation failure after partial workspace mutation retries from the authenticated preparation tree.
  • Regression coverage exercises both stale task-local drift before prepare and a reopened producer closure, not just generic Smithers retry dispatch.

Activity

  1. aviggiano commented on Aug 27, 2026

    @aviggiano
    CollaboratorAuthor

    Production validation of the reopened-producer restore exposed a cleanup-policy edge case. A prior agent execution had created an ignored package-manager binary link under a non-runtime scratch directory. The persisted preparation tree correctly excluded it, and every parent component was a real directory, but removeStaleWorkspaceFiles rejected the candidate because its symlink check included the leaf itself.

    The leaf is safe to unlink: Node removes the directory entry without traversing its target. Only a symlink in a parent component can redirect deletion outside the owned worktree. The draft fix now checks parent components while permitting stale leaf links to be removed. Regression coverage uses a link to an out-of-worktree target, proves the link is removed while the target bytes remain untouched, and separately proves a symlinked parent is still detected. The original strict preparation-tree and dependency-patch authority checks are unchanged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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