Skip to content

Track authoritative terminal state for reconciliation - #1033

Open
dennisonbertram wants to merge 4 commits into
mainfrom
codex/issue-1031-terminal-provenance
Open

Track authoritative terminal state for reconciliation#1033
dennisonbertram wants to merge 4 commits into
mainfrom
codex/issue-1031-terminal-provenance

Conversation

@dennisonbertram

@dennisonbertram dennisonbertram commented Jul 30, 2026

Copy link
Copy Markdown
Owner

Closes #1031

Related #1000
Related #1028
Related #1029

Summary

Distinguishes authoritative harness terminal events from local transport/control placeholders during macOS durable transcript reconciliation. A temporary per-run SSE failure no longer becomes a false success from an older message snapshot, yet it still recovers once the conversation stream delivers the authoritative terminal event. Authoritative run.failed and run.cancelled states remain preserved.

Design

  • RunSession owns terminal provenance and the accepted-run-to-terminal lock because it owns the async server-event boundary.
  • Provenance updates only after event-ID dedupe and only for real queued/started/resumed/terminal harness events.
  • An older durable snapshot is ignored while a run is active or still awaiting its authoritative terminal event.
  • canSubmit remains false during that unresolved interval even after a local stream failure stops the spinner.
  • Reloading the already-selected conversation reconciles in place without cancelling the active stream or clearing provenance; only an actual conversation replacement resets it.
  • Terminal run IDs prevent a late start response from re-locking a fast run whose conversation-wide SSE terminal event arrived before the 202.
  • A redundant per-run stream error arriving after an authoritative terminal event cannot overwrite that terminal state.
  • Local Transcript.markFailed / markCancelled remain UI fallbacks and intentionally do not update authority.
  • Transcript.reconcile receives explicit authoritative state instead of inferring it from rendered runState.

No server, SSE/message wire, persistence, config, provider/model, tool schema, or layout changes.

TDD evidence

Deterministic failing-first regressions observed across the review loop:

  1. A per-run SSE 500 set the local placeholder to .failed; an older durable snapshot then reported .completed, cleared the transport error, and enabled another prompt without any terminal event.
  2. After the first repair, selecting the already-open conversation still cleared the lock through RunSession.load and recreated the false success.
  3. After the second repair, a terminal conversation event arriving after the start request but before its delayed 202 was overwritten by the response and left the completed conversation permanently locked.

Green:

  • transportFailureWaitsForAuthoritativeCompletion drives the same-conversation GUI reload path and preserves .failed, the transport error, and canSubmit == false until delayed authoritative completion arrives, then recovers.
  • terminalBeforeStartResponseDoesNotRelockCompletedRun proves an early authoritative completion stays submit-ready after the delayed 202 and deduped per-run replay.
  • Authoritative failed/cancelled and completed replay controls pass in the same slice.

Verification

Impact / rollout / rollback

Native client-only state provenance; no migration or durable repair. Older clients can retain false failure or false success after a transient disconnect; new clients wait for the authoritative terminal event. Revert if authoritative failure/cancellation stops surviving reconciliation or completed runs cannot recover.

Documentation

Added plan, full impact map, long-term intent, plan index, and required engineering-log symptom/cause/fix/TDD/verification record.

Contract checklist

  • Contract-complete closing issue
  • Owners/callers/similar seams searched
  • Cross-surface map complete
  • Deterministic red observed before code and for all three review defects
  • Existing ownership seam repaired
  • Targeted/full/race/coverage green
  • Compatibility/security/lifecycle/deployment/docs/rollback reconciled

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7b5a9cf434

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +200 to 203
transcript.reconcile(
messages: messages,
authoritativeTerminalState: latestAuthoritativeTerminalState)
connectionError = nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve provisional failure until completion is authoritative

When the per-run SSE fails before harnessd reaches a terminal state, markFailed() makes the session non-busy, so ProjectSession.syncCurrentConversation() can immediately fetch messages and reach this call with latestAuthoritativeTerminalState == nil. For an existing conversation, that endpoint may still return the previous terminal snapshot while the current run continues; Transcript.reconcile then calls load, marks the run completed, and this method clears the connection error. The UI consequently reports success and permits another submission without any completed event or run-status evidence. Absence of authoritative terminal provenance must not be interpreted as completion.

Useful? React with 👍 / 👎.

@cursor

cursor Bot commented Jul 30, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

Copy link
Copy Markdown
Owner Author

Addressed the P1 at 039acd3a with a deterministic red/green regression: an older durable snapshot can no longer report success, clear the transport error, or enable another prompt while the accepted run still lacks an authoritative terminal event. The delayed run.completed event then recovers normally.

Verification is green: strict Swift format; 179 Swift tests / 40 suites; full normal + race + coverage regression gate at 85.6% with zero uncovered functions.

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 039acd3a47

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

streamTask?.cancel()
transcript.load(messages: messages)
latestAuthoritativeTerminalState = nil
awaitingAuthoritativeTerminalState = false

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve the lock when reloading the same conversation

When an accepted run's per-run SSE fails, the transcript becomes non-busy while awaitingAuthoritativeTerminalState intentionally remains true. The user can then click the already-selected conversation in the rail, whose ProjectSession.openConversation path calls load; this assignment clears the lock even though the conversation did not change, while transcript.load reports the potentially older snapshot as completed and the method clears the transport error. canSubmit is consequently re-enabled before any authoritative terminal event arrives, recreating the false-success/overlapping-run scenario through the normal conversation-reload path. Only discard unresolved provenance here when self.conversationID != conversationID.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Addressed the same-conversation reload P1 at 7234c8af. RunSession.load now treats selecting the already-open conversation as in-place reconciliation, preserving the live stream and accepted-run-to-terminal lock. The same deterministic regression now drives that exact GUI reload path and stays locked until the delayed authoritative completion.

Verification remains green: strict Swift format; 179 Swift tests / 40 suites; full normal + race + coverage regression gate at 85.6% with zero uncovered functions.

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7234c8afa8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


let started = try await client.startRun(request)
currentRunID = started.runID
awaitingAuthoritativeTerminalState = true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Avoid re-locking after an early terminal event

When submitting into an already-open conversation, its conversation-wide SSE remains live while startRun awaits the POST response, and harnessd dispatches the run before writing the 202 response. A fast run can therefore deliver its terminal event first, causing apply to clear the lock, only for this assignment to set it again afterward. The subsequent run-scoped replay is discarded by seenEventIDs, so no event clears the lock and the completed conversation permanently rejects further submissions until it is reset or switched. Set the accepted-run lock without overwriting terminal provenance that may already have arrived for started.runID.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Addressed the third P1 at 54756e91: terminal run IDs now prevent a late 202 start response from re-locking a fast run whose authoritative conversation event already completed it. The deterministic regression first reproduced canSubmit == false after the delayed response and now proves the conversation remains submit-ready despite deduped per-run replay.

Verification is green: strict Swift format; 180 Swift tests / 40 suites; full normal + race + coverage regression gate at 85.6% with zero uncovered functions.

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Hooray!

Reviewed commit: 54756e91ed

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@dennisonbertram
dennisonbertram force-pushed the codex/issue-1031-terminal-provenance branch from 54756e9 to b6eba2e Compare July 30, 2026 14:20

Copy link
Copy Markdown
Owner Author

Rebased the previously clean-reviewed tree onto current main (f942c1a5) and preserved the newly merged feedback and race-gate records. Exact head b6eba2e8 is green: strict Swift format; 180 Swift tests / 40 suites; full normal + race + coverage gate at 85.6% with zero uncovered functions.

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b6eba2e81e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// MARK: - Conversation switching

public func load(messages: [StoredMessage], conversationID: String) {
if self.conversationID == conversationID {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reset provenance for destructive conversation reloads

This same-ID fast path also handles the reloads after ProjectSession.undo and rewind, not only rail refreshes. If the removed run failed or was cancelled, reconcilePersistedMessages preserves latestAuthoritativeTerminalState and can re-add that run's error after loading the truncated history, leaving the restored conversation incorrectly marked failed/cancelled. Those destructive callers need a full load that clears terminal provenance, or a distinct refresh API.

AGENTS.md reference: AGENTS.md:L57-L61

Useful? React with 👍 / 👎.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Recover local transport failures during durable transcript reconciliation

1 participant