Passante A Passante publication

En Passante

In Passing
Field Kit

The second ship ran before the first finished

A state-changing action still in flight makes the remote unreadable, so never start a second instance and never grade the first until it returns an exit status.

What happened

On 2026-09-01, a coding assistant launched a second ship command while the first was still running, read a remote that had not updated yet, and reported a failure that had not happened.

2026-09-01 a shell wrapper · a remote state-changing CLI measured: none recorded
3 prompts, in order
  1. Find the in-flight run first You launched a deploy or push again and the reports disagree.
  2. Attribute the final state to one run One line says it landed and another says the gate failed.
  3. Add a single-flight guard You want a duplicate launch blocked, not merely discouraged.

1Find the in-flight run first

Run it when
You launched a deploy or push again and the reports disagree.
It asks you for
The exact command, each launch time, and a process listing for it.
You get back
A verdict on whether an earlier instance is still running, with the evidence line quoted.
It refuses if
No process listing or exit status exists for the earlier run.
copy this into a model
<stance>
The remote may still be changing while you read it. A second launch of a state-changing command does not make the work finish sooner, it destroys the evidence about the first. An agent that reads a ref mid-push will announce a failure that never occurred, and it will sound certain while doing it. Your job here is narrow. Establish whether an earlier instance is still alive before anyone grades the outcome.
</stance>

<inputs>

1. The exact command that was launched, verbatim.
2. The clock time of each launch, in order.
3. A process listing filtered to that command.
4. The shell job list, if the runs started in this shell.
5. The exit status of any run that has already returned.
Ask for each item. Wait for the answer. Do not estimate a launch time. Do not assume a run exited.
</inputs>

<method>

1. Read the process listing. Count live instances of the command. State the count.
2. If the count exceeds one, stop analysing outcomes. Name the oldest instance as the owner.
3. If the count is one, match its start time to a launch. Say which launch owns it.
4. If the count is zero, ask for both exit statuses. A missing status is unknown, not success.
5. Compare the first launch time to the moment the remote was read. Decide whether the read happened during a write.
6. If that read landed inside the first run, mark every conclusion drawn from it as void.
7. If the process listing is unavailable, say the in-flight question stays open. Name the one command that would settle it.

</method>

<deliver>

- In-flight verdict, one line
- The evidence line you read it from
- Which launch owns the live process
- Which earlier conclusions are now void
- What to wait for before touching the remote again

</deliver>

<refuse>

Do not declare the first run failed from a remote read alone. A ref that has not moved during an active push proves nothing. Do not relaunch the command to test the theory. Do not kill a process you cannot attribute to a specific launch. If the user supplies no process listing and no exit status, stop. Say the state is undetermined and ask for one of the two. Refuse to summarise an outcome while a live instance is present. Report the live instance instead and wait.
</refuse>

2Attribute the final state to one run

Run it when
One line says it landed and another says the gate failed.
It asks you for
Both quoted claims, the ref before and after, and each run's identifier.
You get back
A single attribution, or an explicit unknown that names the missing link.
It refuses if
No identifier connects the observed remote change to one invocation.
copy this into a model
<stance>
Conflicting claims about one action cannot both stand. A report that the state landed, paired with a report that the gate rejected it, means the mechanism was never established. Evidence decides. The confident half of the pair is the dangerous half, so resolve the contradiction by attribution rather than by picking the sentence that reads better.
</stance>

<inputs>

1. The exact wording of the success claim.
2. The exact wording of the failure claim.
3. The remote ref before the action, and after.
4. The identifier of each invocation, such as a run id or log file.
5. The log line showing the gate verdict for each invocation.
Ask for all five. Wait on anything missing. Do not reconstruct a log line from memory.
</inputs>

<method>

1. Restate both claims side by side. Name the fact each one asserts.
2. Identify the claim that asserts a state, and the claim that asserts a mechanism.
3. Look for an identifier shared by the state change and one invocation. Quote it.
4. If one invocation's log shows the write completing, attribute the final state to it.
5. If no log shows a completed write, declare the mechanism unknown. Do not pick the likelier run.
6. Check whether the gate verdict belongs to the same invocation as the write.
7. If the gate rejected the run that wrote, say the state landed without passing the gate.
8. State what must be reverted or re-run, given the attribution you can actually support.

</method>

<deliver>

- The two claims, quoted
- The attributed run, or unknown
- The identifier that links state to run
- Whether the gate covered that run
- The next action the evidence supports

</deliver>

<refuse>

Never reconcile the contradiction by assuming both reports describe one run. Do not infer the mechanism from the fact that the state looks correct. A correct-looking remote can come from an unreviewed write. If no identifier links the change to an invocation, stop. Say the mechanism is undetermined and list the logs that would resolve it. Do not call the work complete while the gate verdict belongs to a different run. Do not invent a run id, a timestamp or a log path.
</refuse>

3Add a single-flight guard

Run it when
You want a duplicate launch blocked, not merely discouraged.
It asks you for
The wrapper script, the locking facility available, and the exit code convention.
You get back
A patch adding an exclusive lock, plus a two-shell test that proves refusal.
It refuses if
The proposed lock allows a second run to proceed anyway.
copy this into a model
<stance>
A rule in a document does not stop a second launch. Code does. The guard you write here must make the duplicate invocation exit on its own, with a message naming the run that holds the lock. Advisory warnings fail under pressure, because the operator in a hurry is exactly the one who launches again before the first returns.
</stance>

<inputs>

1. The wrapper script or entry point for the high-consequence action.
2. The locking facility available on this platform.
3. Where the script may write a lock file.
4. The exit code the caller treats as a hard stop.
5. Whether a stale lock from a crashed run must be recoverable.
Ask for each. Wait for the answer. Do not assume a lock directory exists.
</inputs>

<method>

1. Read the entry point. Name the exact line where the remote write begins.
2. Acquire the lock before that line, not after argument parsing alone.
3. Write the current run id and start time into the lock. The second run reads them.
4. On a failed acquisition, print the holder's run id. Exit with the hard-stop code.
5. Release the lock on normal exit and on signal. Show the trap or defer that does it.
6. Handle the stale lock case only if the user asked for it. State the recovery rule in one line.
7. Write a test that starts the action, then runs the same command in a second shell.
8. The test passes only when the second invocation exits non-zero. Record the exit code.
9. If the lock cannot block a second process, say so. Name the mechanism that can.

</method>

<deliver>

- The patch, as a diff
- The line the lock now protects
- The duplicate-launch message
- The two-shell test command
- The observed exit code of the blocked run

</deliver>

<refuse>

Do not ship a guard that only logs a warning. Do not count a code comment or a policy note as the fix. If the only available lock is advisory and the second process can continue, stop. Say the guard is unenforceable here and name what the platform offers instead. Do not claim the guard works without the two-shell test output. Refuse to delete a lock whose holder you cannot confirm is dead.
</refuse>
How you know it worked

Start the action. In a second shell, run the same command again. The second invocation exits non-zero and names the run holding the lock. Then compare the remote ref to the run id the first invocation recorded.

from a real incident, 2026-09-01 · stack a shell wrapper · a remote state-changing CLI · verified by none recorded · concurrencydeploysagentsverification

← every Field Kit