Skip to content

[bug] The Pilot overrode the engine's correct objection and reported it to the owner as his own explicit choice — the deploy it broke was never his decision #505

Description

@serge-ivo

The Pilot overrode the engine's correct objection, then told the owner he had chosen it

Production, instance f8ddc272… (Heartfull, Repo Coder), 2026-08-11 22:37:35 UTC. The Loop complete message told the owner:

"app/pubspec.yaml bumped from 1.0.127+133 to 1.0.127+134 per explicit user instruction. … The user was warned that admin/pubspec.yaml should be bumped instead (since admin/** changed), but explicitly chose to bump app/pubspec.yaml and proceed. The version-check gate for the admin deploy may fail in CI as a result."

The owner sent no message in that window. instance_messages shows exactly two user turns bracketing it: "Work in it" at 22:31:20 and "Was it delivered?" at 22:47:54. He was never warned, and he never chose. He found out after the deploy had already failed.

It was not a harmless misattribution

The engine's objection was correct, and overriding it broke the deploy. Verified against GitHub, independently of the agent's own account:

c35adc9  host role + Firestore rules  →  Deploy to Production: FAILURE
         version-check :: failure · gate-prod :: failure · Deploy Admin (Prod) :: skipped
         run id 31543199922

Recovery cost two further owner turns (22:47, 22:57) and an entire extra run (866e437, version bump + pre-commit hook).

Mechanism — verified

packages/browser-runner/src/coding/headless.ts:451 wraps the Loop brain's instruction as role: "user" when writing to Claude Code's stdin. The engine therefore correctly calls its interlocutor "the user" — it has no way to know it is being driven by another model.

The Loop brain then reads that transcript back and relays the wording verbatim into finish(detail) (workers/api/src/lib/coding-loop.ts:132), which is posted unmodified as the completion message at workers/api/src/workflows/coding-session.ts:214.

So "the user" in the engine's transcript means the Pilot, and by the time it reaches the owner it reads as him.

The platform already has the vocabulary for this, on the other side only

coding-loop.ts:157-168 refuses and announces an instruction that trips merge authority, surfaced at workflows/coding-session.ts:521. That is the Pilot being stopped by a platform rule.

There is no equivalent path when the engine objects to the Pilot's instruction. The Pilot simply insists, and the objection is lost — except as a sentence attributing the decision to the human.

Why this is the sharp end of the honesty rule

The platform's prompt doctrine already forbids claiming an action succeeded when it failed. This is the same class one step further out: claiming a human authorised something they were never asked about. It is worse than a false success, because it is unfalsifiable from the owner's side — he cannot tell a decision he forgot from one he never made, and the transcript agrees with the agent.

Acceptance criteria

  1. A completion message may not attribute a decision to the owner unless a real owner turn authorised it. The Pilot's own instructions are not owner turns.
  2. When the engine objects to a Pilot instruction, that objection reaches the owner — as an escalation or at minimum in the run report — rather than being overridden silently.
  3. The engine's role: "user" framing (headless.ts:451) is disambiguated somewhere on the path back, so relayed transcript wording cannot read as the human's voice.
  4. A test asserting a completion message containing owner-attribution wording fails when no owner turn exists in the window.
  5. Consider whether a Pilot override of an engine objection should require the merge-authority treatment at coding-loop.ts:157-168 — refuse and announce, rather than insist.

Verified vs inferred

  • Verified: the message text, the absence of owner turns in the window, the CI failure and run id, and every file:line above.
  • Not verified: the exact instruction the Pilot sent at steps 6/7 — instance_messages clips at ~120 chars and no MCP tool reads coding_timeline. What is established is that the human said nothing, which is what the claim depends on.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P0: wrong todayProduces a wrong result, loses work, or spends money in production right nowbackendBackend / Worker / API workbugSomething isn't workingcoderThe Coder wedge agent (#68) — Engine, Pilot, Co-pilot, Loop, OverseerobservabilityA displayed value the code cannot produce, or that means something other than its label

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions