Skip to content

loop --dry-run is classified read-only but runLoop still performs live recovery #344

Description

@khaliqgant

Follow-up to #343, which merged as b7c68462. Raised by codex on that PR and verified before filing; deliberately not held against the outage fix because the cloud container never runs loop.

isReadOnlyFactoryCommand classifies loop --dry-run as read-only, so it gets a RelayFleetClient that refuses to mint an identity. But runLoop() still performs live recovery on every iteration regardless of dryRun:

  • factory.ts:4249 runLoop()
  • :4268 and :4273#drainReadyClarificationWake() — can resume or spawn an agent when durable state holds a replied clarification
  • :4274#sweepPrStateCompletions('run-loop') — can release agents when an in-flight PR completed

With only a workspace key configured, those now throw ReadOnlyFleetIdentityError and the loop reports a failure instead of completing the recovery.

runOnce does not share this: #runOnceWithDiscoveryFence gates at if (!(opts.dryRun ?? this.#config.dryRun)), so the dry-run path never reaches the recovery calls. That asymmetry between runOnce and runLoop is the actual defect worth fixing.

Two dispositions, pick with reasoning rather than defaulting:

  1. Make the recovery steps honour dryRun — the more correct fix, since a dry run that resumes agents is not a dry run, and the current behaviour is arguably a pre-existing bug that fix(cli): stop read-only commands registering a relay agent (factory-cloud#55) #343 merely made visible.
  2. Drop loop from the read-only set until (1) lands — narrower and safer, but leaves a command that mints an identity purely to observe.

Prefer (1). If you take (2), say so on this issue so nobody reads the classification as settled.

Either way this needs a must-fire/must-not-fire pair: a loop --dry-run over durable state containing a replied clarification, asserting the recovery either completes or is skipped, and a control proving the test would notice the opposite.

Not urgent and not a dispatch blocker — no container path invokes loop (container/entrypoint.mjs runs only status at :1028 and run-once --dry-run at :1049).

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