Skip to content

🐛 fix: non-transient reconcile error wedges a workload CR permanently (REQ-REL-2026-08) - #186

Merged
konih merged 3 commits into
mainfrom
fix/rel-2026-08-terminal-error-wedge
Aug 6, 2026
Merged

🐛 fix: non-transient reconcile error wedges a workload CR permanently (REQ-REL-2026-08)#186
konih merged 3 commits into
mainfrom
fix/rel-2026-08-terminal-error-wedge

Conversation

@konih

@konih konih commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

P0 — REQ-REL-2026-08

Channel/logistics-app sat at Synced=False for 23h on emb-test after a QM restart: a bare context.DeadlineExceeded escaped the mqrest adapter, setSyncedError treated it as terminal and returned (ctrl.Result{}, nil) — no requeue, no error, no log — and workloadReconcilePredicates had nothing to re-enqueue it. Spec: agent-context/inbox/specs/REQ-REL-2026-08-terminal-error-wedge.md.

Changes

  • REL-2roundTrip/sleepWithContext wrap ctx.Err() in mqadmin.TransientError (with Cause, so errors.Is(err, context.Canceled/DeadlineExceeded) still holds). Deadline expiry now flows down the retry path like every other network failure.
  • REL-1setSyncedError never returns (Result{}, nil) on error:
    • ErrTransientRequeueAfter: TransientRequeueInterval() (unchanged, 30s)
    • ErrTerminal → no requeue (bad spec; generation predicate re-enqueues on fix), now with a distinct default condition reason TerminalError so no-retry states are greppable
    • anything else → error returned to controller-runtime (rate-limited exponential backoff + its ERROR log), per the spec's Q1 recommendation
  • REL-3Synced=False transitions that don't return an error now log the error, reason, and retryAfter (logr has no WARN; ERROR level chosen so the line survives an ERROR|WARN scan).
  • Behavior change: a missing QueueManagerConnection now returns the error (retry + log) instead of relying solely on the QMC-ready watch fan-out; two envtest expectations updated.

Not in this PR: REL-4 (drop desiredMQSC printer column — public-CRD surface, LGTM-gated/HOLD), Q2 (configurable reconcile deadline).

Tests (TDD — all failed before the fix)

  • TestSetSyncedError_UnclassifiedReturnsError — the exact AC regression test: bare DeadlineExceeded must yield RequeueAfter > 0 || err != nil
  • TestQueueReconciler_UnclassifiedErrorSchedulesRetry — same at reconciler level
  • TestRoundTripContextDeadlineExceededIsTransient + strengthened cancel/backoff/sleep tests asserting errors.Is(err, ErrTransient)
  • TestSetSyncedError_TransientLogsRetry — asserts the observable log line
  • classifyReconcileError terminal-without-reason → TerminalError

Gates

test:run (race, coverage 92.6% internal/ ≥ 90% floor), format:check, lint, arch:lint, verify, test:schema, scrub:tree — all green locally.

…s (REQ-REL-2026-08)

A workload CR whose reconcile failed with an error that was neither
TransientError nor TerminalError (e.g. a bare context.DeadlineExceeded
escaping the mqrest adapter after an mqweb blip) returned
(ctrl.Result{}, nil): no requeue, no error, no log. Nothing re-enqueued
the object, so it stayed Synced=False forever (23h live on emb-test).

- REL-2: roundTrip/sleepWithContext wrap ctx.Err() in TransientError so
  deadline expiry and cancellation flow down the retry path.
- REL-1: setSyncedError never returns (Result{}, nil) on error —
  transient gets TransientRequeueInterval, terminal (bad spec) is the
  only no-requeue path, anything unclassified is returned to
  controller-runtime for rate-limited backoff.
- Terminal failures without an explicit reason get a distinct
  ReasonTerminalError condition reason (greppable, no-retry semantics).
- REL-3: Synced=False transitions that do not return an error now log
  the error and scheduled retry interval.
Copilot AI lite review requested due to automatic review settings August 6, 2026 12:48

Copilot AI 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.

Pull request overview

Fixes a controller-runtime wedge where certain non-transient reconcile failures (notably bare context.DeadlineExceeded escaping the mqrest adapter) could leave workload CRs stuck Synced=False indefinitely due to (ctrl.Result{}, nil) being returned (no requeue, no error) and predicates not re-enqueuing.

Changes:

  • Ensure adapter context cancellation/deadline errors are wrapped as mqadmin.TransientError so they follow the transient retry path.
  • Update setSyncedError retry policy: transient errors keep fixed RequeueAfter, terminal errors remain no-requeue, and all other/unclassified errors are returned to controller-runtime for rate-limited backoff + logging.
  • Add/adjust unit+envtest coverage around unclassified error retries, terminal reason classification, and transient “retryAfter” logging.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
internal/controller/reconcile_shared.go Revises error-to-retry mapping and adds logging for non-returned errors.
internal/controller/reconcile_shared_test.go Adds regression tests for unclassified error retry behavior and transient retry logging.
internal/controller/reconcile_error_branches_test.go Updates envtest expectations for missing connection to now return an error (rate-limited retry).
internal/controller/queue_controller_unit_test.go Adds reconciler-level regression test for unclassified DeadlineExceeded retry behavior.
internal/controller/events.go Differentiates terminal condition reason via ReasonTerminalError.
internal/controller/events_test.go Updates expectations for terminal-without-reason classification.
internal/adapter/mqrest/resilience.go Wraps ctx.Err() during request/backoff as mqadmin.TransientError to preserve retry semantics.
internal/adapter/mqrest/resilience_test.go Strengthens/extends tests to assert context errors are transient and preserve errors.Is behavior.
api/v1beta1/queuemanagerconnection_types.go Introduces shared ReasonTerminalError constant for non-retryable failures.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +187 to 193
switch {
case errors.Is(err, mqadmin.ErrTransient):
requeue = ctrl.Result{RequeueAfter: TransientRequeueInterval()}
case errors.Is(err, mqadmin.ErrTerminal):
default:
reconcileErr = err
}
Comment on lines +206 to +207
// The 1h-TTL warning event is the only other trace; without this line a not-synced
// CR is invisible in the logs (controller-runtime only logs returned errors).
@codecov

codecov Bot commented Aug 6, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

…classify LTPA login ctx expiry transient

Review follow-up on PR #186 (REQ-REL-2026-08):
- D1: Synced=False reason tables (INSTALL_AND_USE), OPERATOR_RUNTIME error
  matrix, and ADR-0014 amendment now describe the amended retry policy and
  the distinct TerminalError condition reason.
- R1: ltpa login wraps ctx.Err() in TransientError like roundTrip, with a
  pinning test.
Copilot AI review requested due to automatic review settings August 6, 2026 13:00

Copilot AI 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.

Pull request overview

Copilot reviewed 14 out of 16 changed files in this pull request and generated no new comments.

Files not reviewed (2)
  • api/v1beta1/zz_generated.deepcopy.go: Generated file
  • test/mocks/mqadmin/admin_mock.go: Generated file

task format import-reordered zz_generated.deepcopy.go and admin_mock.go in
e83eb3f; controller-gen/mockery emit the original form, so 'task verify'
went red. Restore both to the generated content.
Copilot AI review requested due to automatic review settings August 6, 2026 13:04

Copilot AI 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.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated no new comments.

@sonarqubecloud

sonarqubecloud Bot commented Aug 6, 2026

Copy link
Copy Markdown

@konih

konih commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

REVIEW — mkurator/fix/rel-2026-08-terminal-error-wedge @ bce9df8 (PR #186) — APPROVE — 2026-08-06

Independent /mkurator-review (tech-reviewer subagent); verdict only — maintainer merges. Review heads: e1fa9cfe83eb3fbce9df8.

Gate Result Notes
go build ./... clean at bce9df8
task verify "verify: ok" — codegen drift (V1) resolved; regenerated output matches commit byte-for-byte
task lint (+arch:lint) clean
task lint:markdown 90 files, 0 errors
task test:run (unit + envtest, -race) all suites pass
coverage internal/ 92.7% (floor 90), api/ 92.5% (floor 75)
task scrub:tree 548 tracked files clean
changelog lane commits parse and render under Bug Fixes / Documentation
helm-test / vuln:check n/a no chart or dependency changes

Finding history:

ID Sev Status
D1 docs drift (reason tables / ADR-0014 vs new retry policy) P1 ✅ resolved in e83eb3f
V1 codegen drift (task format swept generated files) P1 ✅ resolved in bce9df8
R1 LTPA login returned bare ctx.Err() P2 ✅ resolved in e83eb3f (+ pinning test)
F1 mqweb outage/recovery e2e for AC2 P2 open follow-up (tracked in INBOX)
F2 exclude zz_generated.*/test/mocks/ from task format; optional runbook note on bootstrap-ordering ERROR logs P3 open follow-up (tracked in INBOX)

Functional correctness: Full scope delivered and regression-pinned. REL-1: setSyncedError never returns (Result{}, nil) on error — transient → RequeueAfter 30s, terminal → sole no-requeue path with distinct default reason TerminalError, unclassified → error returned for controller-runtime rate-limited backoff; all 25 call sites across the 5 workload controllers (normal + deletion) propagate correctly; QMC controller already backstopped (AUTH-14, 2m). REL-2: ctx.Err() classified transient in roundTrip, sleepWithContext, and LTPA login, with Unwrap preserving errors.Is on the context sentinels; breaker/reauth semantics unchanged. REL-3: ERROR-level "workload CR not synced" line (reason + retryAfter) on every non-error-returning Synced=False path; no secrets in logged errors. REL-4 deliberately deferred (LGTM-gated CRD printer column). The declared behavior change (missing QMC/Secret → returned error + backoff, QMC-ready watch as fast path) is pinned by the two updated envtest expectations. No tautological tests, no hallucinated APIs.

Next steps: clear to merge from this reviewer's standpoint once the fresh head's required checks are green. This review does not authorize the merge and no PR approval was submitted by the reviewer.

@konih
konih merged commit c0f20b2 into main Aug 6, 2026
23 checks passed
konih added a commit that referenced this pull request Aug 6, 2026
…classify LTPA login ctx expiry transient

Review follow-up on PR #186 (REQ-REL-2026-08):
- D1: Synced=False reason tables (INSTALL_AND_USE), OPERATOR_RUNTIME error
  matrix, and ADR-0014 amendment now describe the amended retry policy and
  the distinct TerminalError condition reason.
- R1: ltpa login wraps ctx.Err() in TransientError like roundTrip, with a
  pinning test.
@konih
konih deleted the fix/rel-2026-08-terminal-error-wedge branch August 6, 2026 13:20
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