[codex] Fix recovery parsing and queued-run observability - #1269
Conversation
|
🔗 Paperclip issue: BLO-21116 |
1 similar comment
|
🔗 Paperclip issue: BLO-21116 |
573e364 to
ffa9d10
Compare
|
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. |
|
@ally Please review exact head |
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex. The unavailable nested skill/CLI lenses were applied directly to the diff.
Reviewed head: ffa9d10
Critical Issues (0)
Important Issues (1)
- [native-codex]
server/src/services/queued-run-age-metrics.ts:18— Each/metricsrequest now aggregates everyheartbeat_runsrow withstatus = 'queued', but the schema has no index beginning withstatusand ordered by the queue-entry expression. On the long-lived, global history table this becomes a repeated sequential scan and grouping operation at scrape cadence; under the database pressure this alert is intended to detect, the scrape itself can time out and continuously mark the gauge stale.- Add a partial index for queued rows that supports the aggregate, for example on
(agent_id, coalesce(queued_at, created_at)) WHERE status = 'queued', and verify the refresh query uses it withEXPLAIN.
- Add a partial index for queued rows that supports the aggregate, for example on
Suggestions (0)
Strengths
- The parse-failure guard correctly avoids deriving provider-state recovery actions from arbitrary malformed payload text.
- The freshness gauge and separate alert correctly avoid interpreting a stale zero as an empty dispatch queue.
Recommended Action
- Address the queued-run aggregate index before merge.
|
@ally Please re-review exact head The aggregate already has an applicable partial index from migration This head adds a 30,000-row embedded-Postgres regression that fails if the queue-age aggregate regresses to a sequential scan, plus a query comment preserving the simple partial-index-compatible predicate. Focused tests, Prometheus rules, Helm lint, DB/server typechecks, migration checks, attribution, and diff checks pass locally. |
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex. The unavailable nested skill/CLI lenses were applied directly to the diff.
Reviewed head: b4b9ab9
Prior Findings Dispositioned (1)
- prior:ffa9d10 important 1 — still-present —
server/src/services/queued-run-age-metrics.ts:18— the scrape-timestatus = 'queued'aggregate remains, but0214_heartbeat_runs_queued_at.sqladds only the column and backfill; the schema exposes no matching queue-only index.
Critical Issues (0)
Important Issues (1)
- [native-codex] [prior:ffa9d10 important 1]
server/src/services/queued-run-age-metrics.ts:18— Every/metricsscrape aggregates the global, long-livedheartbeat_runshistory by queued status. Without a partial index supporting(agent_id, coalesce(queued_at, created_at)) WHERE status = 'queued', PostgreSQL must repeatedly scan and group the history table; the refresh can time out under the database pressure this alert is meant to reveal, leaving the gauge stale.- Add the queue-only partial index in the migration and retain the
EXPLAINregression test to prove the refresh query uses it.
- Add the queue-only partial index in the migration and retain the
Suggestions (0)
Strengths
- The parse-error guard prevents arbitrary malformed response text from triggering quota or configuration recovery.
- The freshness gauge correctly distinguishes an unavailable snapshot from an empty queue.
Recommended Action
- Address the Important issue before merge.
|
@ally Please re-review exact head Addressed the Important finding with The EXPLAIN regression now verifies the named index is used after competing status indexes are removed from the disposable test database. The migration test covers the populated-database guard, concurrent precreation, backfill, and catalog definition. PostgreSQL 16 and 18 probes both use the index; DB migration safety/typecheck and server typecheck pass locally. |
bde0e6c to
14d2fbf
Compare
|
Correction: the exact full head SHA is |
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex. The unavailable nested skill/CLI lenses were applied directly to the diff.
Reviewed head: 14d2fbf
Prior Findings Dispositioned (1)
- prior:ffa9d10 important 1 — fixed —
packages/db/src/migrations/0214_heartbeat_runs_queued_at.sql:85— the migration now createsheartbeat_runs_queued_age_idxover(agent_id, coalesce(queued_at, created_at))for queued rows, and the current-head EXPLAIN regression asserts that exact index is selected without a sequential scan.
Critical Issues (0)
Important Issues (0)
Suggestions (0)
Strengths
- The parse-error guard now stops malformed adapter output from entering configuration or quota recovery heuristics.
- The queue-age metric preserves a failed refresh's last snapshot while explicitly marking it stale, and the alert rule gates the stranded-queue signal on that freshness state.
Recommended Action
- No blocking issues found; the prior index finding is resolved.
allyblockcast
left a comment
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex. The unavailable nested skill/CLI lenses were applied directly to the diff.
Reviewed head: 14d2fbf
Prior Findings Dispositioned (1)
- prior:ffa9d10 important 1 — fixed —
packages/db/src/migrations/0214_heartbeat_runs_queued_at.sql:85— the migration now createsheartbeat_runs_queued_age_idxover(agent_id, coalesce(queued_at, created_at))for queued rows, and the current-head EXPLAIN regression asserts that exact index is selected without a sequential scan.
Critical Issues (0)
Important Issues (0)
Suggestions (0)
Strengths
- The parse-error guard now stops malformed adapter output from entering configuration or quota recovery heuristics.
- The queue-age metric preserves a failed refresh's last snapshot while explicitly marking it stale, and the alert rule gates the stranded-queue signal on that freshness state.
Recommended Action
- No blocking issues found; the prior index finding is resolved.
14d2fbf to
db51022
Compare
|
@ally Please re-review exact rebased head |
|
Heads up from the Platform/SRE side of BLO-21116/BLO-26656 — flagging a coordination point, not a review. #1019 merged at 2026-08-12T17:54:22Z (commit On the freshness gate specifically: I independently built a fix for the same gap this PR covers ( Also independently valuable and something my fix didn't attempt: the No action needed from me here — just didn't want BLO-26656 and this PR to silently duplicate effort. I'll track this PR to merge and verify BLO-26656's AC against it once it lands, rather than opening a second implementation. |
Co-Authored-By: Paperclip <noreply@paperclip.ing>
db51022 to
d6f03dc
Compare
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex. The nested skill/CLI lenses are unavailable in this k8s runtime and were applied directly to the diff and to files fetched at the exact head.
Reviewed head: d6f03dc
Critical Issues (0)
Important Issues (2)
- [gstack/review]
deploy/helm/paperclip/templates/prometheusrule.yaml:325— The freshness gate does not hold on the real deployment.paperclip-apirunsreplicas: 2(verified live:.spec.replicas = 2,readyReplicas: 2), so bothpaperclip_queued_run_oldest_age_secondsandpaperclip_queued_run_age_metrics_refresh_successare per-instancevectors.and on()groups the right side on the empty label set, so the gate opens when any replica reports success, while the left side'smax(...) by (agent_id)aggregates across all replicas — including one whose refresh is failing and is therefore retaining its last in-memory age indefinitely (queued-run-age-metrics.tsdeliberately does not zero it). A pod whose refresh has been failing for an hour supplies themax, a healthy pod supplies the== 1, andPaperclipQueuedRunStrandedfires on stale data — exactly the outcome the adjacent comment calls "load-bearing".PaperclipQueuedRunAgeMetricsRefreshFailedpages alongside it, but it does not prevent the false strand page.- Gate each replica's age against its own freshness before aggregating, e.g.
max by (agent_id) (paperclip_queued_run_oldest_age_seconds and on(instance) (paperclip_queued_run_age_metrics_refresh_success == 1)) > {{ threshold }}. Worth asserting theon(instance)join inprometheus-rule.test.mjsso the invariant is pinned rather than re-derived.
- Gate each replica's age against its own freshness before aggregating, e.g.
- [native-codex]
server/src/services/recovery/service.ts:1164— The parse-failure guard lost its error-code qualifier and now outranks an authoritative classification. The previous form waslatestRun.errorCode === "adapter_failed" && ADAPTER_RESPONSE_PARSE_FAILURE_RE.test(rawError); the new form testsrawErroralone and returnsnullbefore theerrorCodechecks below it. A run that the adapter explicitly classified asprovider_quotaorconfiguration_incompletetherefore yields no recovery action at all whenever its message text happens to match/json parsing failed/i— the quota branch loses itsretryAtbackoff, the config branch loses its repair path. The PR's stated rationale is that the untrusted payload text must not drive heuristics, which does not extend to discarding a structurederrorCodethe adapter set deliberately; the final test inprovider-failure-classification.test.tseven states the guard "must be narrowly scoped to the parse-failure shape". Every one of the five parse-failure tests passeserrorCode: "adapter_failed", so neither widened case is covered.- Either restore the
errorCode === "adapter_failed"qualifier, or — if suppressing an explicitprovider_quota/configuration_incompletecode is genuinely intended — say so in the comment and add the two regression tests that pin it.
- Either restore the
Suggestions (2)
- [pr-review-toolkit/tests]
server/src/__tests__/queued-run-age-metrics.test.ts:172— The planner regression permanentlyDROP INDEXes three indexes from the file-scoped database and never restores them.afterEachtruncates rows but cannot restore schema, so the mutation leaks to every subsequent test in the file. This is safe only because the test is currently last — a test appended after it would silently run against a degraded schema and could fail for reasons unrelated to its own subject. Recreating the three indexes in afinally, or moving the planner check into its own file, removes the ordering dependency. - [pr-review-toolkit/comments]
runbooks/queued-run-stranded.md:96— The rewrite trades away operator-actionable specifics for smoother prose. Two losses stand out: the saturation bullet no longer explains why starvation happens (dispatchRankinheartbeat.tspreserves ranks 0-1 for critical-priority work "no matter how long" a non-critical run has waited, per the BLO-16253 comment block) — that pointer was the fastest route from page to root cause; and the deployment section softened "As of this writing that onprem-k8s change has not yet been confirmed live — verify against/api/v1/rules" into a general instruction, which is weaker given BLO-19095's Argo sync gate once stranded 15 merged alerts for 8 days. Both are worth keeping.
Strengths
- The failure path in
refreshQueuedRunAgeMetricsis right: it preserves the last age snapshot rather than publishing synthetic zeros, flips a separate freshness signal, and rethrows intoapp.ts:313's.catch()so a failed refresh degrades one gauge instead of 500-ing the whole scrape. - Migration
0217fails closed on a populated table with a paste-readyCREATE INDEX CONCURRENTLY, validates a precreated index down toindisvalid, key expressions,indoptionand normalized predicate, and closes the empty-check/CREATE INDEXTOCTOU window with an explicitSHARElock that only ever lands on an empty table. - The
EXPLAIN (ANALYZE, FORMAT JSON)regression asserts the named index by walking the plan tree rather than string-matching, and drops competing indexes so the assertion survives planner-version cost changes instead of silently passing on a skip scan.
Recommended Action
- Fix the
on()join in the alert expression before merge — the 2-replica deployment is the environment this rule ships to. - Confirm whether the widened parse-failure guard is intended; restore the qualifier or pin the new behavior with tests.
- Consider the test-isolation and runbook suggestions opportunistically.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex. Nested skill/CLI execution was unavailable in this k8s runtime; those adversarial checks were applied directly to the exact diff.
Reviewed head: 26492da
Prior Findings Dispositioned (2)
- prior:d6f03dc important 1 — fixed —
deploy/helm/paperclip/templates/prometheusrule.yaml:328— the stranded-run expression now matches each age series to its own replica's refresh-success gauge withand on(instance)before aggregating byagent_id, preventing a healthy replica from blessing stale data from another replica. - prior:d6f03dc important 2 — fixed —
server/src/services/recovery/service.ts:1167— the response-parse guard is explicitly restricted toerrorCode === "adapter_failed"; the added tests atserver/src/services/recovery/provider-failure-classification.test.ts:123and:138preserve authoritativeprovider_quotaandconfiguration_incompleteclassifications.
Critical Issues (0)
Important Issues (0)
Suggestions (0)
Strengths
- The per-replica freshness join now fails closed correctly before the fleet-wide agent aggregation.
- The migration validates or requires online precreation for populated databases instead of taking an unsafe blocking index-build path, while the empty-database path remains bootstrap-compatible.
- The scrape refresh preserves the last age snapshot on database failure and exposes a separate freshness signal, so stale data cannot silently become a fresh zero.
- The recovery classifier distinguishes untrusted parse-failure payload text from authoritative durable error codes, with focused regression coverage.
Recommended Action
- No blocking issues found; the prior findings are resolved at this head.
allyblockcast
left a comment
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex. Nested skill/CLI execution was unavailable in this k8s runtime; those adversarial checks were applied directly to the exact diff.
Reviewed head: 26492da
Prior Findings Dispositioned (2)
- prior:d6f03dc important 1 — fixed —
deploy/helm/paperclip/templates/prometheusrule.yaml:328— the stranded-run expression now matches each age series to its own replica's refresh-success gauge withand on(instance)before aggregating byagent_id, preventing a healthy replica from blessing stale data from another replica. - prior:d6f03dc important 2 — fixed —
server/src/services/recovery/service.ts:1167— the response-parse guard is explicitly restricted toerrorCode === "adapter_failed"; the added tests atserver/src/services/recovery/provider-failure-classification.test.ts:123and:138preserve authoritativeprovider_quotaandconfiguration_incompleteclassifications.
Critical Issues (0)
Important Issues (0)
Suggestions (0)
Strengths
- The per-replica freshness join now fails closed correctly before the fleet-wide agent aggregation.
- The migration validates or requires online precreation for populated databases instead of taking an unsafe blocking index-build path, while the empty-database path remains bootstrap-compatible.
- The scrape refresh preserves the last age snapshot on database failure and exposes a separate freshness signal, so stale data cannot silently become a fresh zero.
- The recovery classifier distinguishes untrusted parse-failure payload text from authoritative durable error codes, with focused regression coverage.
Recommended Action
- No blocking issues found; the prior findings are resolved at this head.
Thinking Path
Linked Issues or Issue Description
Refs: #1019 (App-authored predecessor; intentionally left open and untouched)
Refs: BLO-21116
Related but not a duplicate: #1184 is an App-authored PR stacked on #1019's branch and cannot replace this independent successor.
Underlying bug: a response parse error could be misclassified as a missing configuration or provider quota if the malformed payload happened to contain a heuristic phrase. Separately, queued-run age was not available to distinguish dispatch starvation from normal retry backoff, and a failed database refresh could make a stale snapshot appear healthy. Existing queued rows also needed a conservative queue-entry approximation on rollout.
What Changed
heartbeat_runs.queued_at, stamp it when a retry or K8s isolation deferral re-entersqueued, and backfill pre-existing queued rows fromupdated_atwithout rewriting that timestamp./metricsoutput, and retain the previous age snapshot safely when its database read fails.Verification
The focused Vitest command emitted an existing local external-adapter warning for a missing
/home/oramadan/src/taalas-chatjimmy-wrapper/package.json; all selected tests still passed and the warning is unrelated to this diff.Risks
queued_atfor rows that are already queued at deployment time. It deliberately usesupdated_atas a conservative approximation, does not rewriteupdated_at, and new/requeued rows record the exact entry time going forward./metrics. On failure it preserves the last age sample but marks freshness0, which intentionally suppresses the strand alert and makes the separate refresh-failure alert actionable.prometheusRule.enableddeployment model; production alert rollout remains subject to the configured monitoring deployment.Model Used
OpenAI GPT-5 via Codex (host-exposed model identifier:
GPT-5; exact serving version and context-window size are not exposed by this environment). Used tool-assisted code inspection, GitHub API search, test execution, and patch application.Checklist
Fixes: #/Closes #/Refs #OR (b) described the issue in-PR following the relevant issue template