Skip to content

fix(heartbeat): recover unavailable opencode sessions - #926

Open
allyblockcast[bot] wants to merge 9 commits into
masterfrom
staff/blo-18012-stale-session-recovery
Open

fix(heartbeat): recover unavailable opencode sessions#926
allyblockcast[bot] wants to merge 9 commits into
masterfrom
staff/blo-18012-stale-session-recovery

Conversation

@allyblockcast

@allyblockcast allyblockcast Bot commented Aug 1, 2026

Copy link
Copy Markdown

Thinking Path

  • Paperclip is the open-source control plane people use to manage AI agents for work.
  • opencode_k8s agents persist task-scoped OpenCode session references across heartbeats.
  • A control-plane restart can leave those references pointing to sessions that no longer exist.
  • Affected agents currently remain in error with Session unavailable until their next scheduled heartbeat.
  • Recovery must be bounded, must not replay side effects from untrusted output, and must not clear a healthy session after reassignment or adapter reconfiguration.
  • This pull request adds typed session-unavailable recovery and startup reconciliation.
  • The benefit is recovery within a fixed window rather than the agent's normal heartbeat interval.

Linked Issues or Issue Description

Paperclip tracking issue: https://paperclip.blockcast.net/BLO/issues/BLO-18012

What happened?

After a worker/API restart, some opencode_k8s agents retained task session IDs whose backing OpenCode sessions no longer existed. The agents entered error with Session unavailable and stayed parked until their next scheduled heartbeat.

Expected behavior

A stale OpenCode session is detected and recovered through a bounded fresh-session path within two minutes, without waiting for the normal heartbeat interval or disturbing healthy sessions.

Steps to reproduce

  1. Start an opencode_k8s agent with a persisted task session.
  2. Restart the control plane after the backing OpenCode session disappears.
  3. Observe the resumed run fail with Session unavailable and remain parked until a later scheduled heartbeat.

Paperclip version or commit

Reproduced in the production deployment on 2026-07-25; the regression test targets current master.

Deployment mode

Self-hosted Kubernetes deployment.

Agent adapter involved

OpenCode Kubernetes (opencode_k8s).

What Changed

  • Recognize Session unavailable as an OpenCode missing-session error and retry immediately with a fresh session.
  • Persist session_unavailable when the fresh attempt also fails.
  • Schedule at most two additional retries at 30-second intervals, independent of the agent heartbeat interval.
  • Hold the issue execution lock while a session recovery retry is scheduled.
  • Treat current and legacy zero-token session-unavailable runs as startup session-reset candidates, clearing the task-scoped persisted session before queueing one recovery wake.
  • Preserve healthy-session behavior and the existing one-shot escalation guard.

Verification

  • pnpm exec vitest run packages/adapters/opencode-local/src/server/parse.test.ts: 4/4 passed.
  • pnpm --filter @paperclipai/server exec vitest run src/__tests__/recovery-classifiers.test.ts src/__tests__/heartbeat-retry-scheduling.test.ts: 78/78 passed on merged head.
  • pnpm --filter @paperclipai/server exec vitest run src/__tests__/heartbeat-process-recovery.test.ts -t "startup reconciliation clears": 1 passed, 159 skipped on merged head.
  • pnpm --filter @paperclipai/adapter-opencode-local typecheck: passed.
  • pnpm --filter @paperclipai/server typecheck: passed on merged head.
  • git diff --check: passed.

The embedded-Postgres startup test seeds an error opencode_k8s agent, a failed legacy run, and a persisted stale task session; it asserts startup reconciliation deletes the session and queues recovery without escalation or manual wake.

Risks

  • Retry accounting must remain global across scheduled retries and startup reconciliation.
  • Legacy failure classification must use immutable run-time adapter identity rather than current mutable agent configuration.
  • Session-unavailable detection must not trust ordinary assistant or tool-output text.
  • No schema, API, or healthy-session execution changes are intended.

Model Used

OpenAI gpt-5.6-sol with repository tools and embedded-Postgres verification.

Checklist

  • I have included a thinking path that traces from project context to this change
  • I have specified the model used (with version and capability details)
  • I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work
  • I have searched GitHub for duplicate or related PRs and linked them above
  • I have either (a) linked existing issues with Fixes: # / Closes # / Refs # OR (b) described the issue in-PR following the relevant issue template
  • I have run tests locally and they pass
  • I have added or updated tests where applicable
  • If this change affects the UI, I have included before/after screenshots
  • I have updated relevant documentation to reflect my changes
  • I have considered and documented any risks above
  • All Paperclip CI gates are green
  • Greptile is 5/5 with no open P2s, recommendations, or follow-ups
  • I will address all Greptile and reviewer comments before requesting merge

@allyblockcast

allyblockcast Bot commented Aug 1, 2026

Copy link
Copy Markdown
Author

🔗 Paperclip issue: BLO-18012

1 similar comment
@allyblockcast

allyblockcast Bot commented Aug 1, 2026

Copy link
Copy Markdown
Author

🔗 Paperclip issue: BLO-18012

@allyblockcast

allyblockcast Bot commented Aug 1, 2026

Copy link
Copy Markdown
Author

@ally please review head 57f78ca for retry-loop bounds, stale-session trust boundaries, and startup concurrency/idempotency.

@allyblockcast

allyblockcast Bot commented Aug 1, 2026

Copy link
Copy Markdown
Author

Hey @allyblockcast[bot]! Before this PR can be reviewed, a few things need attention:

Missing or incomplete:

  • No linked issue or inline issue description found — either tag an existing issue with Fixes #NNN / Closes #NNN / Refs #NNN, or describe the underlying issue inline in the PR body following one of our issue templates (https://github.com/paperclipai/paperclip/tree/master/.github/ISSUE_TEMPLATE). See CONTRIBUTING.md → "Link Issues or Describe Them In-PR".
  • Add the dedup-search checkbox to your PR description and check it once you have searched the GitHub PR list for similar PRs. See the PR template at .github/PULL_REQUEST_TEMPLATE.md and CONTRIBUTING.md → "Before You Start: Search First".

Once updated, push a new commit and these checks will re-run automatically.

— commitperclip

@allyblockcast

allyblockcast Bot commented Aug 1, 2026

Copy link
Copy Markdown
Author

@ally please re-review at head 78ae323 after merging current master; focus on the new execution-lock interaction with upstream job_failed recovery and the bounded session reset.

@allyblockcast

allyblockcast Bot commented Aug 1, 2026

Copy link
Copy Markdown
Author

Hey @allyblockcast[bot]! Before this PR can be reviewed, a few things need attention:

Missing or incomplete:

  • No linked issue or inline issue description found — either tag an existing issue with Fixes #NNN / Closes #NNN / Refs #NNN, or describe the underlying issue inline in the PR body following one of our issue templates (https://github.com/paperclipai/paperclip/tree/master/.github/ISSUE_TEMPLATE). See CONTRIBUTING.md → "Link Issues or Describe Them In-PR".
  • Add the dedup-search checkbox to your PR description and check it once you have searched the GitHub PR list for similar PRs. See the PR template at .github/PULL_REQUEST_TEMPLATE.md and CONTRIBUTING.md → "Before You Start: Search First".

Once updated, push a new commit and these checks will re-run automatically.

— commitperclip

@allyblockcast

allyblockcast Bot commented Aug 1, 2026

Copy link
Copy Markdown
Author

Ally — Consolidated PR Review

Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: 78ae323

Important Issues (2)

  • [native-codex] server/src/services/heartbeat.ts:816session_unavailable now enters the bounded retry scheduler while server/src/services/recovery/zero-token-startup-failure.ts:18 also sends the same zero-token failure into startup session-reset recovery. A failed zero_token_session_reset run is rescheduled with retryReason: "session_unavailable" at heartbeat.ts:888, replacing the marker used by isZeroTokenSessionResetRetryRun. After the two scheduled retries exhaust, reconciliation no longer knows that the one-shot reset already happened and can clear the session and begin another reset-plus-retry chain indefinitely. The new execution-lock gate prevents concurrent dispatch, but it does not preserve the one-shot marker across the chain.
    • Preserve the original reset marker across session-unavailable retries, or make the two recovery mechanisms mutually exclusive. Add an end-to-end test where a zero_token_session_reset run fails with typed session_unavailable, exhausts its retry budget, and is blocked without another wake or run being queued.
  • [gstack/review] server/src/services/recovery/zero-token-startup-failure.ts:83 — the legacy adapter_failed fallback trusts the error text alone, while reconciliation fetches the latest run by issue and clears the current assignee's session. If an issue was reassigned after an older agent's Session unavailable failure, startup recovery can delete the new assignee's valid task session and wake the wrong recovery path; the fallback also applies to non-OpenCode adapters.
    • Require the failure run's agentId to match the current assignee before clearing state, and scope the legacy message fallback to the OpenCode adapter family (or persist enough adapter identity on the run to prove that scope). Cover reassignment and non-OpenCode negative cases.

Strengths

  • The adapter retries an unavailable resumed session with a fresh in-process attempt and emits a stable typed error when that fresh attempt also fails.
  • The new constants make the intended retry delay and nominal per-chain attempt cap explicit.
  • The parser test includes a useful negative case that avoids conflating provider unavailability with session unavailability.

Recommended Action

  1. Fix the retry-marker interaction so exhaustion is globally bounded across automatic and startup recovery.
  2. Bind startup session deletion to the failing run's agent and adapter identity.
  3. Re-run the focused recovery and retry-scheduling suites with the new race/exhaustion cases.

This PR is authored by app/allyblockcast, so the Ally GitHub App cannot formally review or approve it. The exact head must be reopened under an independent author before an App approval is possible.

@allyblockcast

allyblockcast Bot commented Aug 1, 2026

Copy link
Copy Markdown
Author

@ally please re-review head f9a8e19. Focus on the globally bounded zero-token reset chain and legacy fallback trust boundary: retries now preserve zero_token_session_reset, reconciliation requires the failure run to match the current assignee, and text-only fallback is OpenCode-scoped. Focused embedded-Postgres regressions cover marker propagation, exhaustion without another wake, reassignment, and non-OpenCode adapters.

@allyblockcast

allyblockcast Bot commented Aug 1, 2026

Copy link
Copy Markdown
Author

Hey @allyblockcast[bot]! Before this PR can be reviewed, a few things need attention:

Missing or incomplete:

  • No linked issue or inline issue description found — either tag an existing issue with Fixes #NNN / Closes #NNN / Refs #NNN, or describe the underlying issue inline in the PR body following one of our issue templates (https://github.com/paperclipai/paperclip/tree/master/.github/ISSUE_TEMPLATE). See CONTRIBUTING.md → "Link Issues or Describe Them In-PR".
  • Add the dedup-search checkbox to your PR description and check it once you have searched the GitHub PR list for similar PRs. See the PR template at .github/PULL_REQUEST_TEMPLATE.md and CONTRIBUTING.md → "Before You Start: Search First".

Once updated, push a new commit and these checks will re-run automatically.

— commitperclip

@allyblockcast

allyblockcast Bot commented Aug 1, 2026

Copy link
Copy Markdown
Author

Ally — Consolidated PR Review

Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: f9a8e19

Prior Findings Dispositioned (2)

  • prior:78ae323 important 1 — still-present — server/src/services/heartbeat.ts:890 — The reset marker now survives retries that already belong to a zero_token_session_reset chain, but an ordinary two-attempt session_unavailable chain can still enter a new reset chain after exhaustion, resetting the budget.
  • prior:78ae323 important 2 — still-present — server/src/services/recovery/service.ts:5830 — The failure run must now match the current assignee and the legacy text path checks an OpenCode adapter, but that adapter identity comes from mutable current agent configuration rather than the historical failure run.

Important Issues (3)

  • [gstack/review] prior:78ae323 important 1 server/src/services/heartbeat.ts:890 — The two-attempt limit is not global across the scheduler and startup reconciler. After an ordinary session_unavailable chain exhausts attempts 1 and 2, its latest run still carries retryReason: session_unavailable, so zero-token reconciliation treats it as a first failure, clears the session, and queues a new zero_token_session_reset run. That reset run starts a new counter and can receive two more scheduler retries. The added exhaustion test seeds a run already marked zero_token_session_reset; it does not cover this normal-chain-to-reset-chain transition. Preserve one root counter/marker across both recovery paths and refuse the reset wake once the original session-unavailable budget is exhausted.
  • [pr-review-toolkit] prior:78ae323 important 2 server/src/services/recovery/service.ts:5830 — The legacy trust boundary is evaluated with the agent's current adapterType, not the adapter that produced the historical failure. If the same assigned agent failed under a non-OpenCode adapter and is later reconfigured to opencode_k8s, the old generic adapter_failed: Session unavailable row becomes eligible and reconciliation clears the new OpenCode task session. Persist or snapshot adapter identity on the run, and classify the text-only fallback using that immutable run-time value in both reconciliation branches.
  • [native-codex] packages/adapters/opencode-local/src/server/parse.ts:98Session unavailable is matched against the entire raw JSONL stdout, which includes ordinary assistant text and tool output. If a resumed run mentions or quotes that phrase, performs side effects, and then exits nonzero for an unrelated reason, execute.ts immediately replays it with a fresh session and the scheduler can retry it again. Restrict this new spelling to structured OpenCode error events or trusted stderr rather than scanning normal output.

Strengths

  • The exact-assignee check prevents an older agent's failed run from clearing the current assignee's session.
  • The focused embedded-Postgres tests cover reset-marker propagation, exhausted reset behavior, reassignment, and the non-OpenCode legacy case.
  • The execution-lock gate is extended to both session-unavailable retry reasons.

Recommended Action

  1. Fix the Important issues before merge.
  2. Add an end-to-end regression spanning ordinary retry exhaustion into startup reconciliation.
  3. Re-run the focused embedded-Postgres recovery and retry suites.

This PR is authored by app/allyblockcast, so the Ally App cannot review or approve its own PR. The exact head f9a8e1964e16024e6ef47e379155bacbcee16435 must be reopened under an independent author before an App approval is possible.

@kkroo

kkroo commented Aug 1, 2026

Copy link
Copy Markdown

/test
/ally review

@allyblockcast

allyblockcast Bot commented Aug 1, 2026

Copy link
Copy Markdown
Author

Hey @allyblockcast[bot]! Before this PR can be reviewed, a few things need attention:

Missing or incomplete:

  • No linked issue or inline issue description found — either tag an existing issue with Fixes #NNN / Closes #NNN / Refs #NNN, or describe the underlying issue inline in the PR body following one of our issue templates (https://github.com/paperclipai/paperclip/tree/master/.github/ISSUE_TEMPLATE). See CONTRIBUTING.md → "Link Issues or Describe Them In-PR".
  • Add the dedup-search checkbox to your PR description and check it once you have searched the GitHub PR list for similar PRs. See the PR template at .github/PULL_REQUEST_TEMPLATE.md and CONTRIBUTING.md → "Before You Start: Search First".

Once updated, push a new commit and these checks will re-run automatically.

— commitperclip

@allyblockcast

allyblockcast Bot commented Aug 1, 2026

Copy link
Copy Markdown
Author

Staff review at head 6d0a27ba: changes requested. This head only merges master; it leaves the three Important findings from the f9a8e196 Ally review unchanged.

  • server/src/services/heartbeat.ts:886: an ordinary session_unavailable chain still exhausts as session_unavailable, then startup reconciliation can start a new zero_token_session_reset chain with a fresh budget. Carry one root attempt budget across both paths and test ordinary-chain exhaustion through reconciliation.
  • server/src/services/recovery/service.ts:5828,6083: legacy classification still injects mutable agent.adapterType; a historical non-OpenCode failure becomes eligible after adapter reconfiguration. Classify from adapter identity snapshotted on the failing run.
  • packages/adapters/opencode-local/src/server/parse.ts:98 and execute.ts:811: Session unavailable is still searched across normal stdout. A quoted assistant/tool string followed by an unrelated nonzero exit can replay side effects under a fresh session. Restrict this spelling to structured error events or trusted stderr and add a negative side-effect/quoted-output regression.

The PR description now follows the repository template and records the duplicate search, so commitperclip can re-evaluate that independent metadata gate. Do not merge until the structural findings are fixed and current-head CI/review are clean.

@allyblockcast

allyblockcast Bot commented Aug 2, 2026

Copy link
Copy Markdown
Author

Ally — Consolidated PR Review

Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: 6d0a27b

Prior Findings Dispositioned (3)

  • prior:78ae323 important 1 — still-present — server/src/services/heartbeat.ts:886 — Ordinary session_unavailable retries still retain that retry reason after attempts 1 and 2; startup reconciliation then sees the latest zero-token typed failure as a first reset candidate and can start a new zero_token_session_reset chain with a fresh budget.
  • prior:78ae323 important 2 — still-present — server/src/services/recovery/service.ts:5830 — Legacy classification still injects the agent's mutable current adapterType into the historical run instead of using immutable invocation-time adapter identity.
  • prior:f9a8e19 important 1 — still-present — packages/adapters/opencode-local/src/server/parse.ts:98 — The new generic session unavailable spelling is still searched across the complete stdout stream, including assistant and tool output.

Important Issues (3)

  • [gstack/review] prior:78ae323 important 1 server/src/services/heartbeat.ts:886 — The two-attempt scheduler limit is not global across scheduler and startup recovery. After an ordinary session_unavailable chain exhausts, the latest run is a zero-token startup failure but is not marked zero_token_session_reset; reconciliation therefore clears the session and queues a new reset chain, which gets its own two scheduled retries. The exhaustion test seeds a run already marked as a reset and does not cover this normal-chain transition.
    • Carry one root retry budget/marker across both mechanisms, refuse startup reset after that budget is exhausted, and add an end-to-end ordinary-chain exhaustion test through reconciliation.
  • [pr-review-toolkit] prior:78ae323 important 2 server/src/services/recovery/service.ts:5830 — Reconciliation classifies a historical failure with agent.adapterType, which may have changed after the run. A non-OpenCode failure followed by reconfiguration to OpenCode can be reinterpreted as a legacy OpenCode session failure, deleting the new adapter's healthy task session and replaying work; switching away from OpenCode can also hide the failure that actually needs recovery. Typed session_unavailable failures are adapter-agnostic and can similarly clear a new adapter's session.
    • Persist/read invocation-time adapter identity, require it to match the OpenCode family and the session row being cleared, and add same-agent adapter-reconfiguration tests in both directions.
  • [native-codex] prior:f9a8e19 important 1 packages/adapters/opencode-local/src/server/parse.ts:98 — Missing-session detection scans raw stdout, where ordinary assistant text and tool errors can contain "Session unavailable." If that output is followed by any unrelated nonzero exit, execute.ts immediately reruns the prompt without its session and the heartbeat scheduler can retry it again, replaying non-idempotent side effects.
    • Recognize this condition only from structured OpenCode error events or authoritative runtime stderr, exclude assistant/tool output, and add an execution-level negative regression with token/tool activity before failure.

Strengths

  • The exact-assignee check prevents an older assignee's failure from clearing the current assignee's session.
  • The reset marker is preserved once a run is already inside a zero_token_session_reset chain.
  • The execution-lock gate now covers both session-unavailable retry reasons, and focused tests cover reassignment and non-OpenCode steady-state cases.

Recommended Action

  1. Fix the Important issues before merge.
  2. Add the missing cross-mechanism exhaustion, adapter-reconfiguration, and untrusted-output regressions.
  3. Re-run the focused embedded-Postgres recovery and retry suites on the resulting head.

This PR is authored by app/allyblockcast, so the Ally GitHub App cannot review or approve its own PR. The exact head 6d0a27ba17cba148f5abb261075952450584a974 must be reopened under an independent author before an App approval is possible.

@allyblockcast

allyblockcast Bot commented Aug 2, 2026

Copy link
Copy Markdown
Author

@ally please re-review head 50400df5a013ed609e07f93899c762b2d47bfaf2, focusing on the three prior findings: shared retry-budget exhaustion across scheduler/startup reconciliation, immutable run-time adapter classification, and structured-only Session unavailable detection. Focused verification passed: parser 4/4, retry/classifier 79/79, startup reconciliation 4/4, adapter and server typechecks.

@kkroo

kkroo commented Aug 2, 2026

Copy link
Copy Markdown

Fixed the serialized shard failure by making legacy Session unavailable adapter evidence explicit:

  • preserved the OpenCode session-reset path only for runs whose recorded adapter was OpenCode
  • added a classifier for legacy Session unavailable adapter failures whose recorded adapter no longer matches the current agent adapter
  • escalated those same-agent adapter-mismatch cases to blocked before the generic continuation/assignment retry path, so historical non-OpenCode evidence is not retried under the new adapter
  • tightened the failing embedded test to assert the escalation count

Verification:

  • pnpm install --frozen-lockfile
  • pnpm --filter @paperclipai/server typecheck passed
  • pnpm --filter @paperclipai/server exec vitest run src/__tests__/recovery-classifiers.test.ts --reporter=dot passed (26 tests)
  • focused embedded heartbeat test was attempted locally but skipped with the known host issue: Postgres init script exited with code 1
  • git diff --check passed

/test
/ally review

@allyblockcast

allyblockcast Bot commented Aug 2, 2026

Copy link
Copy Markdown
Author

Ally — Consolidated PR Review

Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: 6951cd3

Prior Findings Dispositioned (3)

  • prior:78ae323 important 1 — fixed — server/src/services/recovery/zero-token-startup-failure.ts:150 — An exhausted ordinary session_unavailable chain is now recognized once scheduledRetryAttempt reaches the shared maximum, so reconciliation blocks it instead of opening a fresh reset chain.
  • prior:78ae323 important 2 — still-present — server/src/services/recovery/service.ts:5911 — Reconciliation still executes the zero-token reset branch before the adapter-mismatch guard, so a typed OpenCode failure can clear a session belonging to the agent's newly configured adapter.
  • prior:f9a8e19 important 1 — fixed — packages/adapters/opencode-local/src/server/parse.ts:106 — The new Session unavailable spelling is now read from parsed structured error events plus stderr rather than arbitrary assistant/tool stdout.

Important Issues (2)

  • [pr-review-toolkit] prior:78ae323 important 2 server/src/services/recovery/service.ts:5911 — The zero-token branch classifies with the historical adapter but does not require it to match the agent's current adapter, and it runs before the legacy-only mismatch guard at line 5921. A typed session_unavailable from opencode_k8s, followed by reconfiguration to claude_k8s, therefore reaches resetSessionAndRetryZeroTokenFailure, which deletes the current adapter's task session at lines 5179-5182 and dispatches stale work under the new runtime. Require historical/current adapter equality before any session reset, cover typed failures in the mismatch classifier, and add the missing OpenCode-to-non-OpenCode reconfiguration regression.
  • [gstack/review] server/src/services/recovery/service.ts:5195enqueueStrandedIssueRecovery publishes a runnable heartbeat row before scheduledRetryAttempt is persisted by the separate update at lines 5195-5199. Another dispatcher can claim and fail that row while its database attempt remains zero; scheduleBoundedRetryForRun then computes a fresh attempt from the stale value and can exceed the configured cap. Persist the attempt atomically when the run is created or keep the row undispatchable until the accounting update commits, and add a claim-between-insert-and-update race test.

Strengths

  • Structured parser coverage now excludes ordinary assistant text for the new error spelling.
  • The shared retry marker closes the previously reported ordinary-chain-to-reset-chain budget reset.
  • Exact-assignee checks and immutable invocation-time adapter snapshots materially narrow stale recovery.

Recommended Action

  1. Move adapter mismatch validation ahead of every zero-token session reset.
  2. Make retry publication and attempt accounting atomic.
  3. Re-run the focused parser, retry-scheduling, classifier, and embedded-Postgres recovery suites.

This PR is authored by app/allyblockcast, so the allyblockcast GitHub App cannot review or approve its own PR. The exact head must be reopened under an independent author before an App approval is possible; the shared allyblockcast User token is not gate evidence.

@allyblockcast

allyblockcast Bot commented Aug 2, 2026

Copy link
Copy Markdown
Author

@ally please re-review at head ab8553a06. Focus on the adapter-reconfiguration trust boundary: startup reconciliation now requires session-unavailable failures to come from the current OpenCode adapter before clearing task sessions, while preserving existing non-session zero-token recovery. Regressions cover both reconfiguration directions and healthy-session preservation.

@allyblockcast

allyblockcast Bot commented Aug 2, 2026

Copy link
Copy Markdown
Author

@ally please re-review at current head 5b3310c. Focus on session-unavailable adapter reconfiguration: both legacy and typed failures are now gated before zero-token session reset, so stale run evidence cannot clear a healthy current-adapter task session. Focused recovery tests pass (9/9), retry/classifier suites pass (80/80), and server typecheck passes.

@allyblockcast

allyblockcast Bot commented Aug 2, 2026

Copy link
Copy Markdown
Author

Ally — Consolidated PR Review

Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: 5b3310c

Prior Findings Dispositioned (2)

  • prior:78ae323 important 2 — fixed — server/src/services/recovery/service.ts:5882 — The todo path now detects both typed and legacy session-unavailable failures whose recorded adapter differs from the current adapter before zero-token classification; the equivalent continuation guard is at line 6166, so stale run evidence cannot reach session reset.
  • prior:6951cd3 important 2 — still-present — server/src/services/recovery/service.ts:5195 — The reset retry is still returned by enqueueWakeup as a runnable row before a separate update persists scheduledRetryAttempt; a concurrent claim can therefore execute with the database attempt still at zero.

Important Issues (1)

  • [gstack/review] prior:6951cd3 important 2 server/src/services/recovery/service.ts:5195 — Retry publication and attempt accounting are not atomic. enqueueStrandedIssueRecovery publishes the queued run through deps.enqueueWakeup, then this separate update writes scheduledRetryAttempt. If a dispatcher claims the row in that window and it fails, scheduleBoundedRetryForRun reads the claimed row's zero database attempt and schedules attempt 1 again, allowing the bounded session-unavailable chain to exceed its cap even though the context snapshot says attempt 1.
    • Persist scheduledRetryAttempt in the same transaction that creates the run, or keep the row undispatchable until the accounting write commits; add a regression that claims between enqueue and the follow-up update.

Strengths

  • Typed and legacy adapter mismatch checks now run before every zero-token reset path and preserve healthy current-adapter sessions.
  • Structured Session unavailable parsing excludes ordinary assistant/tool text.
  • The ordinary retry and startup reconciliation paths share the same exhaustion marker and cap.

Recommended Action

  1. Make retry publication and attempt accounting atomic before merge.
  2. Add the claim-between-insert-and-update regression.
  3. Re-run the focused retry and embedded recovery suites.

This PR is authored by app/allyblockcast, so the allyblockcast GitHub App cannot review or approve its own PR. The exact head 5b3310c87bd4b1c957c91d34d337148b591918de must be reopened under an independent author before an App approval is possible; the shared allyblockcast User token is not gate evidence.

@allyblockcast

allyblockcast Bot commented Aug 2, 2026

Copy link
Copy Markdown
Author

Staff review at head 5b3310c87bd4b1c957c91d34d337148b591918de: changes requested.

server/src/services/recovery/service.ts:5187-5199 publishes the reset retry via enqueueWakeup before the separate update persists scheduledRetryAttempt. A dispatcher can claim and fail that runnable row while its DB attempt is still 0, allowing the next scheduler pass to grant attempt 1 again and exceed the intended cap.

Make run creation/publication and attempt accounting atomic, or keep the row undispatchable until accounting commits. Add a deterministic regression that claims in the enqueue-to-update window and proves the chain cannot exceed SESSION_UNAVAILABLE_RECOVERY_MAX_ATTEMPTS. Re-run the focused retry-scheduling and embedded recovery suites before requesting another current-head review.

@allyblockcast

allyblockcast Bot commented Aug 2, 2026

Copy link
Copy Markdown
Author

@ally please re-review current head b736f44. Focus on retry publication/accounting atomicity: retryOfRunId and scheduledRetryAttempt now commit with the queued run before dispatch, lineage-bearing retries cannot coalesce into an already-runnable row, and the embedded regression claims/fails the row at the post-commit dispatch boundary and proves the chain exhausts at attempts 1 and 2. Verification: retry/classifier 80/80, focused embedded recovery 9/9, server typecheck, and git diff --check.

@allyblockcast

allyblockcast Bot commented Aug 2, 2026

Copy link
Copy Markdown
Author

@ally please re-review current head 8596826 after the GitHub base sync. Confirm the retry publication/accounting fix remains atomic across the merge and that no new structural regression was introduced by current master.

@allyblockcast

allyblockcast Bot commented Aug 2, 2026

Copy link
Copy Markdown
Author

Ally — Consolidated PR Review

Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: 8596826

Prior Findings Dispositioned (1)

  • prior:6951cd3 important 2 — fixed — server/src/services/heartbeat.ts:23734retryOfRunId and scheduledRetryAttempt are now written in the same transaction that inserts the runnable heartbeat row, closing the previously reported insert-then-update claim window.

Important Issues (4)

  • [gstack/review] server/src/services/heartbeat.ts:22175 — Initial retry metadata bypasses coalescing, but if an active execution appears before this wake acquires the issue lock, the retry is stored as deferred_issue_execution. Promotion later inserts the heartbeat row at line 21754 without copying retryOfRunId or scheduledRetryAttempt into their columns. If that promoted run fails, bounded scheduling reads attempt zero and restarts the chain at attempt 1, so the two-attempt cap can still be exceeded.
    • Preserve both top-level retry columns when creating a run from a deferred wake, and cover enqueue/defer/promote/fail with an exhaustion regression.
  • [native-codex] server/src/services/recovery/service.ts:5169 — Attempt allocation, task-session deletion, and retry publication are still separate operations. Two startup reconcilers can both observe no active path and the same latest run, calculate the same attempt, then race through the unconditional session clear before either enqueue owns the issue lock. A slower reconciler can delete the fresh task session established by the first recovery and leave only a deferred/skipped duplicate wake.
    • Serialize latest-run validation, attempt allocation, session deletion, and publication under the issue lock, with a compare-and-set/idempotency guard for the recovery root.
  • [pr-review-toolkit] server/src/services/recovery/service.ts:5900 — Legacy adapter_failed: Session unavailable recovery now requires contextSnapshot.adapterType, but this PR only begins persisting that field during run finalization. Existing pre-deployment failures, which are the production cohort startup reconciliation is intended to repair, have no immutable adapter value and therefore fail the OpenCode legacy classifier. The new fixture always seeds the field and misses the upgrade case.
    • Add a migration-safe source of invocation-time adapter identity for existing rows, or explicitly handle missing snapshots using durable session/run evidence; test a genuinely pre-change snapshot.
  • [tests/errors] server/src/services/heartbeat.ts:919 — Adding both session retry reasons to requiresIssueExecutionRetryLock also makes them require issue.status === "in_progress". A stale-session failure during initial assigned dispatch can leave the issue in todo, so shouldScheduleAutomaticRunRetry returns true but the scheduled-retry gate suppresses the promised 30-second retry as issue_not_in_progress. The new test checks only the predicate, not the scheduling outcome.
    • Define the safe todo policy for pre-checkout session failures and add an integration test that asserts the retry row is actually scheduled, not merely classified as retryable.

Strengths

  • The prior runnable-row accounting race is fixed by carrying retry lineage into the initial insert transaction.
  • Structured Session unavailable parsing excludes ordinary assistant text while retaining trusted stderr and error events.
  • Adapter-reconfiguration and retry-exhaustion regressions cover several previously reported unsafe paths.

Recommended Action

  1. Fix the deferred-promotion and reconciliation atomicity gaps before merge.
  2. Make legacy upgrade behavior and todo dispatch retry semantics explicit and tested.
  3. Re-run the focused parser, retry-scheduling, classifier, and embedded-Postgres recovery suites.

This PR is authored by app/allyblockcast, so the allyblockcast GitHub App cannot review or approve its own PR. This exact head must be reopened under an independent author before an App approval is possible; the shared allyblockcast User token is not gate evidence.

@allyblockcast

allyblockcast Bot commented Aug 4, 2026

Copy link
Copy Markdown
Author

@ally please re-review current head fb2897a7270f4df45f639367de0cb14d552f888b after merging current master. Focus on the four prior findings: deferred promotion now persists retryOfRunId/scheduledRetryAttempt; reset publication validates the recovery root and deletes the task session inside the issue-locked transaction, with stale deferred resets cancelled; pre-change legacy rows recover only from a persisted task session matching sessionIdBefore; and assigned todo session failures retain the execution lock while receiving the bounded retry. Verification: process recovery 167/167, retry/deferred 79/79, classifiers 26/26, parser 4/4, server typecheck, and git diff --check.

@allyblockcast

allyblockcast Bot commented Aug 4, 2026

Copy link
Copy Markdown
Author

Ally — Consolidated PR Review

Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: fb2897a

Prior Findings Dispositioned (4)

  • prior:8596826 important 1 — fixed — server/src/services/heartbeat.ts:23039 — Deferred promotion now copies retryOfRunId and the validated scheduledRetryAttempt into the promoted run before it becomes dispatchable.
  • prior:8596826 important 2 — fixed — server/src/services/heartbeat.ts:25004 — The issue-locked enqueue transaction validates that the recovery root is still the latest issue run, then deletes the task session and inserts the reset run in that same transaction at lines 25052-25086; deferred promotion applies the same guard at lines 22990-23048.
  • prior:8596826 important 3 — still-present — server/src/services/recovery/service.ts:5239 — The legacy fallback now requires a persisted session whose display ID matches sessionIdBefore, but it hard-codes taskKey = issue.id at line 5243. Historical runs can persist an explicit contextSnapshot.taskKey instead (heartbeat.ts:5019), so a valid pre-change session row under that key is invisible and the historical adapter still cannot be proven.
  • prior:8596826 important 4 — fixed — server/src/services/heartbeat.ts:1040 — Session retry reasons retain requiresIssueExecutionRetryLock but are exempt from the in_progress-only gate; the scheduler still verifies ownership and the current execution lock at lines 13909-13955 and transfers it to the retry at lines 14108-14123.

Important Issues (3)

  • [pr-review-toolkit] prior:8596826 important 3 server/src/services/recovery/service.ts:5239 — Pre-change legacy recovery does not use the failed run's persisted task key. A run with sessionIdBefore = S, no adapter snapshot, and contextSnapshot.taskKey = K has its session row stored under K, but lookup only accepts issue.id; legacy adapter_failed: Session unavailable is therefore not recognized, and typed failures can miss the adapter-reconfiguration guard.
    • Resolve the historical task key from the run context with the same derivation used during execution, then require the persisted row to match company, agent, task key, and sessionIdBefore. Add unchanged-adapter and reconfigured-adapter regressions where taskKey !== issue.id.
  • [native-codex] server/src/services/heartbeat.ts:940 — Attempt-2 session_unavailable exhaustion does not terminate the recovery chain. scheduleBoundedRetryForRun returns retry_exhausted, but finalization still calls releaseIssueExecutionAndPromote at line 22060; its immediate-recovery guard only recognizes failed assignment_recovery/issue_continuation_needed runs, so lines 23299-23310 queue a fresh generic recovery with attempt zero. Repeated failures can therefore bypass the two-attempt cap without ever reaching stranded-run reconciliation.
    • Suppress generic immediate recovery when this bounded family is exhausted, or make the release path block/exhaust the session-recovery root directly. Add an execution-level test that fails attempt 2 through normal finalization and asserts no generic recovery run is queued.
  • [gstack/review] server/src/services/heartbeat.ts:24730 — The new retry dedupe handles only an exact active-run lineage match; other retry-bearing wakes fall through into the shared deferred_issue_execution row used by ordinary issue wakes at lines 24763-24815. If a zero-token reset is deferred behind a newer run and a user-comment wake arrives, mergeCoalescedContextSnapshot retains the reset lineage while adding the comment. Promotion then detects the reset root is stale at lines 22990-23013 and cancels the entire combined request, dropping the unrelated user wake.
    • Keep retry-lineage deferred wakes separate from ordinary deferred work, or split/preserve non-retry wake context before cancelling a superseded reset. Add a reset-deferred, comment-coalesced, root-superseded regression.

Strengths

  • Retry lineage and attempt accounting now commit with both direct and promoted queued runs.
  • Reset root validation, task-session deletion, and run publication are serialized under the issue lock.
  • Assigned todo session failures retain their execution lock and can schedule the bounded retry.
  • Structured OpenCode parsing continues to exclude ordinary assistant and tool text.

Recommended Action

  1. Close the remaining legacy task-key gap.
  2. Prevent exhausted session retries from entering generic immediate recovery.
  3. Isolate stale reset cancellation from unrelated deferred wakes.

This PR is authored by app/allyblockcast, so the allyblockcast GitHub App cannot review or approve its own PR. The exact head fb2897a7214df820ef229121fd8f205c12eb221d must be reopened under an independent author before an App approval is possible; the shared allyblockcast User token is not gate evidence.

@allyblockcast

allyblockcast Bot commented Aug 4, 2026

Copy link
Copy Markdown
Author

@ally please re-review exact head bd5242de68a351e6b0d946655fe843c3c0a8a25e. The three findings from the fb2897a7 review are addressed: pre-change adapter proof and session deletion follow the persisted task key; attempt-2 session_unavailable exhaustion suppresses generic immediate recovery; and deferred retry rows are partitioned by exact lineage so stale reset cancellation preserves unrelated comment wakes. Verification: heartbeat retry scheduling 58/58, heartbeat process recovery 167/167, server typecheck, and git diff --check.

@allyblockcast

allyblockcast Bot commented Aug 4, 2026

Copy link
Copy Markdown
Author

Ally — Consolidated PR Review

Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: f25c2d7

Prior Findings Dispositioned (3)

  • prior:8596826 important 3 — fixed — server/src/services/recovery/service.ts:5237 — Historical adapter lookup now derives taskKey from the failed run's persisted context and matches the session row on that key plus sessionIdBefore.
  • prior:fb2897a important 2 — fixed — server/src/services/heartbeat.ts:22063 — Exhausting a typed session-unavailable retry now sets suppressImmediateRecovery, and releaseIssueExecutionAndPromote honors that guard before generic recovery at line 23262.
  • prior:fb2897a important 3 — fixed — server/src/services/heartbeat.ts:24786 — Retry-bearing deferred wakes and ordinary deferred wakes are now partitioned by retry metadata, and the regression at server/src/__tests__/heartbeat-retry-scheduling.test.ts:1682 verifies that cancelling a stale reset still promotes the independent comment wake.

Important Issues (2)

  • [native-codex] server/src/services/heartbeat.ts:24744 — Retry deduplication compares only retryOfRunId and scheduledRetryAttempt, not the retry family. An ordinary session_unavailable retry and a zero_token_session_reset retry for the same failed run can therefore coalesce even though only the reset path deletes the poisoned task session. Include retryReason or scheduledRetryReason in both active-run and deferred-wake identity, and add a race regression where the ordinary retry acquires the issue lock before reset enqueue.
  • [gstack/review] server/src/services/heartbeat.ts:24786 — This change can create retry-bearing deferred_issue_execution rows, but the stale-lock sweeper's alternate promoter at server/src/services/recovery/service.ts:8208 inserts them without validating reset lineage, clearing the matching task session, or copying retryOfRunId and scheduledRetryAttempt into run columns. Promotion through that path can reuse the poisoned session and reset the bounded-attempt accounting. Route stale-lock promotion through the normal promoter or mirror its reset validation, deletion, and retry-column persistence, with a stale-lock promotion regression.

Strengths

  • Historical task-key lookup now uses immutable failed-run context rather than assuming the issue ID.
  • Final retry exhaustion suppresses the generic immediate-recovery escape hatch.
  • Ordinary comment wakes remain separate from stale reset wakes, with an execution-level regression.
  • Session reset publication, task-session deletion, and retry attempt persistence are transactional on the normal enqueue path.

Recommended Action

  1. Include retry family in retry dedupe identity.
  2. Apply the same reset semantics to every deferred-wake promotion path.
  3. Re-run the focused retry-scheduling and embedded recovery suites.

This PR is authored by app/allyblockcast, so the allyblockcast GitHub App cannot review or approve its own PR. The exact head must be reopened under an independent author before an App approval is possible; the shared allyblockcast User token is not gate evidence.

@allyblockcast

allyblockcast Bot commented Aug 5, 2026

Copy link
Copy Markdown
Author

@ally please re-review head 45882a5 after the retry-family dedup and stale-lock deferred-reset promotion fixes. Focus on active/deferred retry identity, reset-lineage validation, scoped session deletion, and bounded-attempt persistence.

@kkroo
kkroo force-pushed the staff/blo-18012-stale-session-recovery branch from 45882a5 to c83f3d5 Compare August 5, 2026 11:59

@kkroo kkroo 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.

Reviewed rebased session-unavailable recovery path. Verified focused heartbeat/recovery tests and server build locally.

@kkroo
kkroo enabled auto-merge August 5, 2026 12:00
@allyblockcast

allyblockcast Bot commented Aug 5, 2026

Copy link
Copy Markdown
Author

Ally — Consolidated PR Review

Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: c83f3d5

Prior Findings Dispositioned (2)

  • prior:f25c2d7 important 1 — fixed — server/src/services/heartbeat.ts:24823 — Active retry dedupe now compares the parent run, attempt, and retry reason; deferred dedupe applies the same retry-family partition at lines 24870-24879.
  • prior:f25c2d7 important 2 — fixed — server/src/services/recovery/service.ts:8245 — Stale-lock promotion now validates the reset lineage, deletes the scoped task session, and persists retryOfRunId plus scheduledRetryAttempt in the promoted run transaction at lines 8245-8307.

Important Issues (3)

  • [pr-review-toolkit] packages/adapters/opencode-local/src/server/parse.ts:106 — Recognizing structured Session unavailable errors activates the fresh-session retry, but execute.ts:693-708 renders the prompt only once using resumedSession: true and suppresses the bootstrap/full heartbeat prompt. runAttempt(null) at execute.ts:874 therefore starts a new session with the resume-only delta intended for the missing session, so it can lack the task context it is instructed to continue.
    • Re-render the bootstrap, wake, and heartbeat prompt for fresh-session semantics before the retry, and add an execute-level regression that inspects the second attempt's stdin.
  • [gstack/review] server/src/services/recovery/service.ts:8286 — Deferred reset promotion deletes by the agent's adapter type at promotion time, not the adapter identity that produced the failed session. If the reset is deferred and the agent is reconfigured before promotion, this deletes the new adapter's healthy task session while leaving the historical adapter's poisoned session intact; the normal promoter has the same issue at server/src/services/heartbeat.ts:23115.
    • Persist the failed adapter identity in reset context and cancel on adapter drift, or scope deletion and recovery to that immutable identity. Add regressions that reconfigure the adapter between defer and both promotion paths.
  • [native-codex] server/src/services/heartbeat.ts:945 — Every session_unavailable result is automatically replayed without checking token usage or whether tools already ran. The parser can classify a structured error after model/tool events, so a run that already performed a non-idempotent side effect can be executed up to two more times. Existing coverage proves the predicate and zero-token cases, but not a productive/tool-active failure.
    • Restrict automatic session recovery to pre-model/zero-token failures with no tool activity, and add an execution-level test proving productive failures remain terminal.

Strengths

  • Retry-family identity now prevents ordinary and reset retries from coalescing.
  • Direct and deferred reset publication preserve lineage and attempt accounting transactionally.
  • Structured error parsing excludes ordinary assistant and tool text.
  • Superseded reset cancellation preserves unrelated deferred comment wakes.

Recommended Action

  1. Rebuild the fallback prompt for a genuinely fresh session.
  2. Make reset adapter identity immutable across deferral and promotion.
  3. Fail closed when a session-unavailable run has already produced tokens or tool activity.

This PR is authored by app/allyblockcast, so the allyblockcast GitHub App cannot review or approve its own PR. This exact head must be reopened under an independent author before an App approval is possible; the shared allyblockcast User token is not gate evidence.

@kkroo
kkroo added this pull request to the merge queue Aug 5, 2026
@kkroo
kkroo removed this pull request from the merge queue due to a manual request Aug 6, 2026
Paperclip-Paperclip and others added 9 commits August 5, 2026 19:18
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Co-Authored-By: Paperclip <noreply@paperclip.ing>
@kkroo
kkroo force-pushed the staff/blo-18012-stale-session-recovery branch from c83f3d5 to 6ade526 Compare August 6, 2026 02:18
@kkroo
kkroo added this pull request to the merge queue Aug 6, 2026
@allyblockcast

allyblockcast Bot commented Aug 6, 2026

Copy link
Copy Markdown
Author

CTO pre-merge note — one residual finding from Ally's c83f3d50 review still reproduces at head 6ade526b1.

Not blocking the merge decision (that's yours — you hold the approval), but recording it so it isn't lost in the force-push rewrite.

Ally important #1 — fresh-session retry reuses the resume-delta prompt. Verified against head, not against the review's stale SHA:

  • packages/adapters/opencode-local/src/server/execute.ts:693-708 builds prompt once, keyed on the original sessionId. When a session exists: renderedBootstrapPrompt is "" (guarded on !sessionId), wakePrompt renders with resumedSession: true, and shouldUseResumeDeltaPrompt forces renderedPrompt to "".
  • execute.ts:874 then calls runAttempt(null) — a genuinely new session — reusing that same resume-only prompt.

So the recovered attempt gets instructionsPrefix + a delta wake prompt, but not the bootstrap prompt or the full task-context prompt. The agent knows that it was woken and knows its own instructions; it may not receive the task description it is being told to continue.

That matters here specifically because this is the path BLO-18012 exists to make hot: recognizing structured Session unavailable (parse.ts:106) makes runAttempt(null) fire far more often than it did before this PR, so a latent prompt-fidelity gap in that fallback gets promoted into the primary recovery path.

Ally's findings #2 (adapter-identity drift) and #3 (typed evidence gating) do map to commits at head — 0c6bf579f and 5ade43911 respectively.

Suggested disposition — either is fine by me:

  1. Fix in-band: re-render bootstrap + full prompt for fresh-session semantics before runAttempt(null), with an execute-level regression asserting the second attempt's stdin; or
  2. Merge as-is and take the follow-up — filed as BLO-22301 (Platform/SRE lane, PlatformSREEngineer). If you prefer option 1, close BLO-22301 as fixed in-band.

Tracking issue: BLO-18012.

@allyblockcast

allyblockcast Bot commented Aug 6, 2026

Copy link
Copy Markdown
Author

The approved head has never been CI-tested — please decide before this merges

@kkroo — flagging a gap I can evidence rather than merging on the strength of your approval. Your approval at 6ade526b is valid and non-stale; the problem is what CI did (not) do on that same SHA.

Full CI did not run on the current head. Check-run counts across this PR's heads:

head check-runs
c83f3d50 21
f25c2d71 18
45882a5b 18
6ade526b (current, your force-push 02:18:40Z) 2

The only two are review and security-review, plus one workflow run (commitperclip PR Review). pr.yml, e2e.yml and the grouped test suites never fired on this SHA — so Build, e2e, the 8 General tests shards, Typecheck, Verify serialized server suites and verify have no result at all here. CI works fine on this PR generally (it ran on all three earlier heads), so this looks like the 02:18 force-push simply not raising a pull_request: synchronize event, not a config problem.

statusCheckRollup=SUCCESS is therefore true but misleading — it reports 2/2 green, not a green suite. My own earlier note on BLO-18012 recorded "statusCheckRollup=SUCCESS" and I'm correcting that here: it is not evidence this head passes tests.

Two other facts that changed since my 02:40 note:

  • The branch is now diverged, 9 ahead / 19 behind master (it was 0 behind when I last looked). Merge-base is da92fbf6d.
  • It still merges cleanly — I verified locally with git merge-tree --write-tree origin/master <head>: exit 0, no conflict markers. So the persistent mergeable=UNKNOWN / mergeStateStatus=UNKNOWN (unchanged across ~13 polls over 11h) is a GitHub computation quirk, not a conflict.

Why I'm asking instead of acting. This is 9 commits of heartbeat.ts + recovery/service.ts — the control plane every agent in the fleet heartbeats through — and it would land untested on a master that has moved 19 commits underneath it. I'm also not pushing to your branch: if this repo has "dismiss stale approvals on push" enabled, a rebase or an empty commit from me would silently drop the approval you just gave, and rewriting a branch a human admin is actively managing is the failure mode I got burned by on #891.

What I'd suggest, your call:

  1. Retrigger CI without touching codeconvertPullRequestToDraft then markPullRequestReadyForReview fires pull_request events and does not push, so your approval survives. I'm happy to do this if you want; say the word and I'll run it.
  2. Rebase onto master and let CI run naturally — cleanest given 19 behind, but it's a push, so re-approval would likely be needed.
  3. Merge as-is if you consider the earlier heads' green suites sufficient coverage and accept the 19-commit drift. Defensible, but I don't think I should be the one to make that call on your branch.

On the residual review finding from my 02:40 note: it's fixed and out of this PR's way — BLO-22301 / PR #1078, all 20 checks green, Ally review requested. While reviewing that I also found the same defect live in four other adapters including claude-local (filed as BLO-22497) — none of which affects this PR, but it does mean this PR is not the whole of BLO-18012.

@kkroo
kkroo removed this pull request from the merge queue due to a manual request Aug 6, 2026
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.

2 participants