Skip to content

OLS-3566 Add TTL lifecycle CRD fields and reconciler enforcement - #412

Open
sriroopar wants to merge 2 commits into
openshift:mainfrom
sriroopar:ols-3566-ttl-lifecycle
Open

OLS-3566 Add TTL lifecycle CRD fields and reconciler enforcement#412
sriroopar wants to merge 2 commits into
openshift:mainfrom
sriroopar:ols-3566-ttl-lifecycle

Conversation

@sriroopar

Copy link
Copy Markdown

Summary

  • Add automatic garbage collection of terminal AgenticRun resources following the Kubernetes Jobs ttlSecondsAfterFinished pattern
  • Introduces AgenticOLSConfig.spec.lifecycle.terminalTTL as a cluster-wide default TTL and AgenticRun.spec.ttlAfterTerminal as a per-run mutable override
  • Operator stamps status.terminalTime and spec.ttlAfterTerminal when a run reaches terminal state, then deletes expired runs or requeues with RequeueAfter for the remaining duration

CRD Changes

Field Type Description
AgenticOLSConfig.spec.lifecycle.terminalTTL *int32 (optional) Cluster-wide default TTL in seconds for terminal runs
AgenticRun.spec.ttlAfterTerminal *int32 (optional, mutable) Per-run TTL override; 0 disables auto-deletion
AgenticRun.status.terminalTime *metav1.Time (optional) Timestamp when the run first reached terminal state (set once)

Reconciler Changes

  • New handleTerminalTTL() method integrated into all terminal phase branches (Completed, Failed, Denied, Escalated, EmergencyStopped, NoActionRequired)
  • Stamps terminalTime and ttlAfterTerminal on first terminal reconcile
  • Pre-set ttlAfterTerminal (by adapter/admin) is never overwritten
  • ttlAfterTerminal=0 explicitly disables auto-deletion for that run
  • No AgenticOLSConfig CR → no auto-deletion (backwards-compatible)
  • fanOutToActiveRuns updated to enqueue terminal runs needing TTL stamping on config changes

Files Changed

File Change
api/v1alpha1/agenticolsconfig_types.go Added LifecycleConfig struct and Lifecycle field
api/v1alpha1/agenticrun_types.go Added TTLAfterTerminal to spec, TerminalTime to status
controller/agenticrun/reconciler.go Added handleTerminalTTL(), integrated into all terminal branches
controller/agenticrun/helpers.go Added getTerminalTTL() helper
controller/agenticrun/ttl_test.go 9 new unit tests covering all acceptance criteria
config/crd/bases/*.yaml Regenerated via make manifests

Test plan

  • make manifests regenerates CRD YAMLs without error
  • make test passes (all existing + 9 new TTL tests)
  • make api-lint passes
  • Verify terminal runs get terminalTime and ttlAfterTerminal stamped
  • Verify expired runs are deleted after TTL elapses
  • Verify ttlAfterTerminal=0 prevents auto-deletion
  • Verify pre-set ttlAfterTerminal is not overwritten by cluster default
  • Verify no AgenticOLSConfig CR means no auto-deletion

Spec Reference: lightspeed-agentic-operator/.ai/spec/what/agentic-lifecycle.md — rules 1-10

🤖 Generated with Claude Code

@openshift-ci
openshift-ci Bot requested review from joshuawilson and xrajesh August 4, 2026 03:48
@openshift-ci

openshift-ci Bot commented Aug 4, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign onmete for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added configurable automatic cleanup for completed, failed, or otherwise terminal runs.
    • Added cluster-wide default and per-run retention settings, with per-run values taking precedence.
    • Added timestamps showing when runs became terminal.
    • Expired terminal runs are deleted automatically; active runs are unaffected.
    • Runs are retained when cleanup is disabled or no retention period is configured.
    • Terminal timestamps reset when a run is revised and returns to an active state.
  • Documentation

    • Documented terminal-run retention and cleanup settings.
  • Tests

    • Added coverage for configuration, retention behavior, expiration, requeueing, and terminal run outcomes.

Walkthrough

Changes

AgenticRun now supports cluster-wide and per-run terminal TTL settings. The controller records terminal timestamps, persists TTL values, requeues unexpired runs, and deletes expired runs. Tests and lifecycle specifications cover terminal phases, configuration states, and revision resets.

AgenticRun terminal cleanup

Layer / File(s) Summary
TTL contracts and lifecycle state
api/v1alpha1/agenticolsconfig_types.go, api/v1alpha1/agenticrun_types.go
Defines cluster lifecycle TTL configuration, per-run TTLAfterTerminal, and status TerminalTime.
Terminal TTL reconciliation
controller/agenticrun/helpers.go, controller/agenticrun/reconciler.go, controller/agenticrun/handlers.go
Loads TTL configuration, stamps terminal runs, synchronizes analyzed generation, preserves configured values, requeues unexpired runs, deletes expired runs, and clears stale timestamps during revision.
TTL validation and lifecycle documentation
controller/agenticrun/ttl_test.go, controller/agenticrun/handlers_test.go, .ai/spec/what/crd-api.md, .ai/spec/what/run-lifecycle.md
Tests terminal phases, disabled cleanup, requeueing, expiration, revision resets, and generation synchronization. Documentation describes the API and lifecycle rules.

Sequence Diagram(s)

sequenceDiagram
  participant AgenticRunReconciler
  participant AgenticOLSConfig
  participant KubernetesAPI
  AgenticRunReconciler->>AgenticOLSConfig: Read lifecycle terminal TTL
  AgenticRunReconciler->>KubernetesAPI: Persist TerminalTime and TTLAfterTerminal
  AgenticRunReconciler->>AgenticRunReconciler: Calculate expiration
  AgenticRunReconciler->>KubernetesAPI: Requeue until expiration
  AgenticRunReconciler->>KubernetesAPI: Delete expired AgenticRun
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.08% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the added TTL lifecycle fields and reconciler enforcement.
Description check ✅ Passed The description directly explains the TTL garbage-collection changes, CRD fields, reconciler behavior, and test plan.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@controller/agenticrun/reconciler.go`:
- Around line 278-281: Update the enqueue condition in the reconciler around
DerivePhase and isTerminal so terminal runs are enqueued whenever either
TerminalTime or TTLAfterTerminal is missing. Preserve enqueueing for all
non-terminal runs and the existing handling for terminal runs with incomplete
TTL metadata.

In `@controller/agenticrun/ttl_test.go`:
- Line 98: Check every getAgenticRun call in controller/agenticrun/ttl_test.go
at lines 98, 141, 280, 314, and 350: capture its returned error and call
t.Fatalf before dereferencing got, preserving the existing status/spec
assertions after successful retrieval.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 364a3193-6b60-4ee6-8602-6f3929d5792c

📥 Commits

Reviewing files that changed from the base of the PR and between 5d52adf and 6550c99.

⛔ Files ignored due to path filters (2)
  • config/crd/bases/agentic.openshift.io_agenticolsconfigs.yaml is excluded by !config/crd/bases/**
  • config/crd/bases/agentic.openshift.io_agenticruns.yaml is excluded by !config/crd/bases/**
📒 Files selected for processing (5)
  • api/v1alpha1/agenticolsconfig_types.go
  • api/v1alpha1/agenticrun_types.go
  • controller/agenticrun/helpers.go
  • controller/agenticrun/reconciler.go
  • controller/agenticrun/ttl_test.go
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • openshift/lightspeed-agentic-sandbox (manual)

Comment thread controller/agenticrun/reconciler.go Outdated
Comment thread controller/agenticrun/ttl_test.go Outdated
@sriroopar

Copy link
Copy Markdown
Author

Adversarial review

This is the origin of the TTL/lifecycle feature that #413 cherry-picks on top of. I checked out the branch, built it (go build ./... clean), ran the tests (all pass), and read the diff plus surrounding reconciler/spec context. The code here is essentially identical to what I already reviewed in #413's cherry-picked commit — same two critical bugs apply, since they're the same lines.

🔴 Critical

1. Likely nil-pointer panic in handleTerminalTTL from a cached-client re-Get racing the informer cache

if run.Status.TerminalTime == nil {
    base := run.DeepCopy()
    run.Status.TerminalTime = &now
    if err := r.statusPatch(ctx, run, base); err != nil { ... }
}

if run.Spec.TTLAfterTerminal == nil {
    clusterTTL, err := getTerminalTTL(ctx, r.Client)
    ...
    if clusterTTL != nil {
        // Re-fetch to avoid conflicts after the status patch above.
        if err := r.Get(ctx, client.ObjectKeyFromObject(run), run); err != nil { ... }
        original := run.DeepCopy()
        run.Spec.TTLAfterTerminal = clusterTTL
        ...
    }
}
...
terminalTime := run.Status.TerminalTime.Time  // <-- can panic

r.Client is mgr.GetClient() — reads go through the informer cache, writes go straight to the API server. On the first reconcile where a run turns terminal and a cluster-wide terminalTTL is already configured: we stamp Status.TerminalTime locally and patch it, then immediately r.Get(...), which overwrites the entire run struct with whatever the cache currently holds. If the watch hasn't observed the write yet (a normal race — cache sync isn't instantaneous), the fetched copy still has Status.TerminalTime == nil, clobbering the value just set. We then fall through to run.Status.TerminalTime.Time → nil pointer dereference.

This is timing-dependent so it won't always fire, but it's a real crash risk on every terminal AgenticRun once cluster TTL is configured. ttl_test.go uses fake.NewClientBuilder(), which is always fully consistent, so it structurally can't catch this.

The re-Get also looks unnecessary: client.MergeFrom (without WithOptimisticLock) doesn't require a fresh resourceVersion, and controller-runtime's Patch/Status().Patch() already decode the server's response back into the passed object. Simplest fix: drop the re-fetch and patch off the in-memory object directly.

2. TTL stamping bumps metadata.generation, which can spuriously re-arm the revision workflow

crd-api.md rule 6 currently documents spec.revisionFeedback as "Only mutable spec field", and needsRevision() treats any generation > Analyzed.observedGeneration as "a new revision was requested." RevisionFeedback is never cleared after being processed (no code path resets it), and revision is explicitly supported even from a terminal (NoActionRequired) phase per run-lifecycle.md rule 6.

handleTerminalTTL does a plain spec Patch to set TTLAfterTerminal:

original := run.DeepCopy()
run.Spec.TTLAfterTerminal = clusterTTL
if err := r.Patch(ctx, run, client.MergeFrom(original)); err != nil { ... }

Any spec write bumps metadata.generation for a CRD with a status subresource, regardless of which field changed — this PR introduces the second mutable spec field on AgenticRun, breaking the implicit "only revisionFeedback changes generation" invariant that needsRevision() relies on. Concretely: an advisory-only run (spec.execution unset) that already went through one revision cycle, with stale non-empty spec.revisionFeedback left in spec, will have needsRevision() spuriously flip back to true the reconcile after ttlAfterTerminal gets stamped — re-triggering a full re-analysis (new LLM call, new AnalysisResult) for a run with nothing new to revise, and skipping the TTL cleanup path on that reconcile. No test in ttl_test.go exercises the TTL-stamp × revisionFeedback interaction, so this is invisible to CI.

🟠 Should fix

3. Spec docs not updated, and the PR description cites a spec file that doesn't exist

The PR body says "Spec Reference: lightspeed-agentic-operator/.ai/spec/what/agentic-lifecycle.md — rules 1-10" — there is no agentic-lifecycle.md anywhere in the repo (.ai/spec/what/ has run-lifecycle.md, not that). Separately, per this repo's own convention (crd-api.md's "Planned Changes" section: "specs MUST be updated when v1alpha1 changes"), this PR adds AgenticOLSConfig.spec.lifecycle.terminalTTL, AgenticRun.spec.ttlAfterTerminal, and AgenticRun.status.terminalTime to the API but doesn't touch crd-api.md's "Configuration Surface" section or run-lifecycle.md's terminal-phase behavioral rules at all.

🟡 Minor / nits

  • spec.ttlAfterTerminal has no CEL immutability guard, unlike nearly every other AgenticRun spec field (request, targetNamespaces, analysisOutput, tools, analysis, execution, verification are all immutable-after-set). Anyone with patch access can rewrite it at any time, including after the operator stamps the cluster default, silently overriding the admin's lifecycle policy for that run. May be intentional (self-service opt-out) — worth a one-line doc callout either way.
  • The Failed case in reconciler.go changed from return r.handleFailed(ctx, &run) to if result, err := r.handleFailed(ctx, &run); err != nil { return result, err }, which silently discards result when err == nil. Harmless today since handleFailed always returns ctrl.Result{} on success, but a latent footgun if that function ever grows a requeue path.
  • The fanOutToActiveRuns predicate — !isTerminal(phase) || p.Status.TerminalTime == nil || p.Spec.TTLAfterTerminal == nil — re-enqueues every terminal run whose ttlAfterTerminal is still nil on every ApprovalPolicy/AgenticOLSConfig/watched-ConfigMap change, forever, if no cluster TTL is ever configured (there's nothing to "stamp" in that case, so these reconciles are pure churn). Not incorrect, just worth bounding if terminal-run counts get large.

What looks solid

  • getTerminalTTL's not-found/no-lifecycle handling, TestHandleTerminalTTL_PresetTTLNotOverwritten, and the zero-disables-deletion / no-config-no-deletion tests are correct and well covered.
  • Finalizer interaction: handleTerminalTTL's r.Delete correctly relies on the existing RBAC/templog finalizer cleanup path.
  • Generated CRD YAML matches the Go type changes (make manifests was run).

Given 1–2 are real correctness bugs invisible to the existing test suite, I'd hold this for a fix + regression test before merge — and note that #413 currently duplicates this entire diff via an unsquashed cherry-pick commit, so fixing it here (the origin) and rebasing #413 on top once merged would avoid the two copies drifting apart.

@sriroopar
sriroopar force-pushed the ols-3566-ttl-lifecycle branch 2 times, most recently from 7de7501 to a1d4cdc Compare August 10, 2026 21:02

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
controller/agenticrun/ttl_test.go (1)

73-116: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add a test that preserves the first terminal timestamp.

Run reconciliation twice for the same terminal run. Store TerminalTime after the first reconciliation. Assert that the second reconciliation leaves that value unchanged.

Without this regression test, a future timestamp refresh can postpone expiry on every reconciliation and prevent TTL deletion.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controller/agenticrun/ttl_test.go` around lines 73 - 116, The test
TestHandleTerminalTTL_StampsTerminalTimeAndTTL should reconcile the same
terminal run twice, save TerminalTime after the first reconciliation, then
assert the second reconciliation preserves the identical timestamp. Keep the
existing TTL assertions and verify the timestamp is not refreshed between
reconciliations.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.ai/spec/what/crd-api.md:
- Line 57: Clarify the TTL policy in the specification, especially rules 48 and
23, by stating whether an absent AgenticOLSConfig disables only default TTL
stamping or also deletion of already pre-set spec.ttlAfterTerminal values. Align
handleTerminalTTL and the related reconciliation tests with that explicit
policy, preserving pre-set TTL behavior if the intended contract is that
configuration controls defaults only.

In @.ai/spec/what/run-lifecycle.md:
- Line 65: The watched-configuration/policy/ConfigMap fan-out rule must only
re-enqueue terminal runs for TTL stamping when an effective cluster terminal TTL
is configured; do not enqueue runs solely because spec.ttlAfterTerminal is unset
when AgenticOLSConfig is absent or terminalTTL is unset. Keep any terminalTime
stamping behavior as a separate, explicit path.
- Line 57: Update the revision-detection logic described by rules 20 and 24 so a
change containing only spec.ttlAfterTerminal does not satisfy needsRevision,
including when spec.revisionFeedback is non-empty; distinguish revisionFeedback
changes from TTL-only changes while preserving operator TTL-write handling. Add
a regression test covering a user or adapter TTL-only update with non-empty
revisionFeedback and verify that re-analysis is not started.
- Around line 61-64: The run lifecycle must start a new terminal TTL epoch after
revision feedback. Update the revision-start transition for NoActionRequired
runs to clear or replace status.terminalTime before the next terminal state,
ensuring TTL deletion and RequeueAfter calculations use the new terminal
timestamp; add a regression test covering revision followed by
re-terminalization.

---

Nitpick comments:
In `@controller/agenticrun/ttl_test.go`:
- Around line 73-116: The test TestHandleTerminalTTL_StampsTerminalTimeAndTTL
should reconcile the same terminal run twice, save TerminalTime after the first
reconciliation, then assert the second reconciliation preserves the identical
timestamp. Keep the existing TTL assertions and verify the timestamp is not
refreshed between reconciliations.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e51380d3-0fb5-469a-86ba-89a9b8452b30

📥 Commits

Reviewing files that changed from the base of the PR and between ebb339e and a1d4cdc.

⛔ Files ignored due to path filters (1)
  • config/crd/bases/agentic.openshift.io_agenticruns.yaml is excluded by !config/crd/bases/**
📒 Files selected for processing (5)
  • .ai/spec/what/crd-api.md
  • .ai/spec/what/run-lifecycle.md
  • api/v1alpha1/agenticrun_types.go
  • controller/agenticrun/reconciler.go
  • controller/agenticrun/ttl_test.go
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • openshift/lightspeed-agentic-sandbox (manual)
🚧 Files skipped from review as they are similar to previous changes (2)
  • controller/agenticrun/reconciler.go
  • api/v1alpha1/agenticrun_types.go

Comment thread .ai/spec/what/crd-api.md Outdated
Comment thread .ai/spec/what/run-lifecycle.md
Comment thread .ai/spec/what/run-lifecycle.md Outdated
Comment thread .ai/spec/what/run-lifecycle.md Outdated
@sriroopar
sriroopar force-pushed the ols-3566-ttl-lifecycle branch from a1d4cdc to 5844651 Compare August 10, 2026 23:59

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.ai/spec/what/crd-api.md:
- Line 57: Update the AgenticOLSConfig.spec.lifecycle.terminalTTL description to
state that pre-set spec.ttlAfterTerminal: 0 disables deletion, while only
non-zero values remain eligible. Replace the claim that runs without a
configured default are never auto-deleted with the rule 23 behavior: a later
effective terminalTTL is applied retroactively to terminal AgenticRun resources
lacking spec.ttlAfterTerminal.

In `@controller/agenticrun/handlers_test.go`:
- Line 820: Update the test calls to getAgenticRun in the affected test cases to
capture and assert the returned errors instead of discarding them. Ensure each
failed read causes the test to fail while preserving the existing agentic-run
assertions.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 14c7a961-9e89-470f-ac71-69a3685a401a

📥 Commits

Reviewing files that changed from the base of the PR and between a1d4cdc and 5844651.

⛔ Files ignored due to path filters (1)
  • config/crd/bases/agentic.openshift.io_agenticruns.yaml is excluded by !config/crd/bases/**
📒 Files selected for processing (7)
  • .ai/spec/what/crd-api.md
  • .ai/spec/what/run-lifecycle.md
  • api/v1alpha1/agenticrun_types.go
  • controller/agenticrun/handlers.go
  • controller/agenticrun/handlers_test.go
  • controller/agenticrun/reconciler.go
  • controller/agenticrun/ttl_test.go
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • openshift/lightspeed-agentic-sandbox (manual)
🚧 Files skipped from review as they are similar to previous changes (4)
  • controller/agenticrun/ttl_test.go
  • .ai/spec/what/run-lifecycle.md
  • controller/agenticrun/reconciler.go
  • api/v1alpha1/agenticrun_types.go

Comment thread .ai/spec/what/crd-api.md Outdated
Comment thread controller/agenticrun/handlers_test.go Outdated
@sriroopar
sriroopar force-pushed the ols-3566-ttl-lifecycle branch 2 times, most recently from 6415171 to 736f092 Compare August 11, 2026 00:33

@blublinsky blublinsky left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Three error paths in handleTerminalTTL return raw errors without wrapping via fmt.Errorf("%s: %w", ErrFoo, err), which is inconsistent with the project convention and with the rest of this file.

The statusPatch errors for stamping terminalTime, the r.Patch error for stamping ttlAfterTerminal, and the statusPatch error for advancing observedGeneration are all logged but returned unwrapped — while other error paths in the same method (getTerminalTTLErrStampTerminalTTL, r.DeleteErrDeleteExpiredRun) correctly use the fmt.Errorf("%s: %w", …) pattern.

These are the only unwrapped Patch/statusPatch error returns in the entire file. The existing ErrStampTerminalTTL constant is the natural fit for all three, e.g.:

return ctrl.Result{}, false, fmt.Errorf("%s: %w", ErrStampTerminalTTL, err)

@sriroopar
sriroopar force-pushed the ols-3566-ttl-lifecycle branch from 736f092 to aa77989 Compare August 11, 2026 12:23

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.ai/spec/what/crd-api.md:
- Around line 12-14: The revision-detection logic in needsRevision must
distinguish user revisions from operator-only spec.ttlAfterTerminal updates, so
TTL-only generation changes do not re-enter analysis for terminal
NoActionRequired, advisory-only Completed, or execution-less Failed runs. Update
the corresponding lifecycle specification to document this exception, while
preserving re-analysis for genuine revisionFeedback changes, and add regression
tests covering each affected terminal scenario in addition to the existing
TTL-stamping test.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 35fb90bb-2edc-4a6a-b40d-e9581b5ed16b

📥 Commits

Reviewing files that changed from the base of the PR and between 5844651 and aa77989.

📒 Files selected for processing (4)
  • .ai/spec/what/crd-api.md
  • controller/agenticrun/handlers_test.go
  • controller/agenticrun/reconciler.go
  • controller/agenticrun/ttl_test.go
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • openshift/lightspeed-agentic-sandbox (manual)
🚧 Files skipped from review as they are similar to previous changes (3)
  • controller/agenticrun/reconciler.go
  • controller/agenticrun/ttl_test.go
  • controller/agenticrun/handlers_test.go

Comment thread .ai/spec/what/crd-api.md Outdated
@sriroopar
sriroopar force-pushed the ols-3566-ttl-lifecycle branch from a360871 to 2b49daa Compare August 11, 2026 12:33

@blublinsky blublinsky left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review: OLS-3566 TTL lifecycle

Summary

Clean implementation of terminal-run garbage collection following the Kubernetes Jobs ttlSecondsAfterFinished pattern. Spec documentation, test coverage, and backwards compatibility are all strong.

Issue: crash-recovery desync in handleTerminalTTL (must-fix)

Location: controller/agenticrun/reconciler.go, handleTerminalTTL

The observedGeneration sync (step 3 of the three-patch sequence) is nested inside if run.Spec.TTLAfterTerminal == nil. If the reconciler crashes between the spec patch (which stamps ttlAfterTerminal and bumps metadata.generation) and the status patch (which syncs Analyzed.observedGeneration), the generation sync is permanently skipped on subsequent reconciles — because TTLAfterTerminal is no longer nil, the entire block is bypassed.

For runs that carry stale revisionFeedback (non-empty, never cleared by design), needsRevision() returns true due to the stale observedGeneration. Several terminal phase blocks (NoActionRequired, advisory-only Completed, execution-less Failed) use needsRevision() as a gate before reaching handleTerminalTTL, so the run exits the terminal branch and spuriously re-enters analysis.

Suggested fix: Move the observedGeneration sync outside the if run.Spec.TTLAfterTerminal == nil block — make it an unconditional idempotent check:

// Unconditional: heal stale observedGeneration regardless of how
// ttlAfterTerminal was set (crash recovery, pre-set by adapter, etc.)
if analyzed := meta.FindStatusCondition(run.Status.Conditions,
    agenticv1alpha1.AgenticRunConditionAnalyzed); analyzed != nil &&
    analyzed.ObservedGeneration < run.Generation {
    base := run.DeepCopy()
    analyzed.ObservedGeneration = run.Generation
    if err := r.statusPatch(ctx, run, base); err != nil {
        return ctrl.Result{}, false, fmt.Errorf("%s: %w", ErrStampTerminalTTL, err)
    }
}

This fires only during recovery (when generation is already synced, no API call is made).

What's good

  • Thorough test coverage — 9 tests covering stamping, idempotency, preset preservation, zero-disables, expiry, requeue, no-config, and the critical observedGeneration sync. The three regression tests (NoActionRequired, advisory Completed, execution-less Failed) show deep understanding of the revision-loop interaction.
  • Spec rules 23/24 and 6a/6b/48 are precise and internally consistent.
  • handleRevision correctly clears TerminalTime to prevent stale timestamps across revision cycles.
  • Fan-out logic is well-reasoned: only re-enqueues terminal runs for TTL stamping when a cluster default actually exists, avoiding churn.
  • Full backwards compatibility: no AgenticOLSConfig CR = no auto-deletion.

@sriroopar
sriroopar force-pushed the ols-3566-ttl-lifecycle branch from 2b49daa to 91d953b Compare August 11, 2026 17:51

@blublinsky blublinsky left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should-fix: doc references non-existent sub-rule "23a"

crd-api.md rule 6b says "rule 6/23a" but run-lifecycle.md has no labeled sub-rule 23a — only rule 23 with unlabeled markdown sub-bullets. Should be "rule 6/23" (the first sub-bullet of rule 23 covers clearing terminalTime on revision). Note: #413 already has this fixed to "rule 6/23".

Suggest doing a broader sweep of all spec/doc files to ensure consistency with the actual implementation — especially around rule numbering, cross-references, and behavioral claims.

…gression tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@sriroopar
sriroopar force-pushed the ols-3566-ttl-lifecycle branch from 91d953b to fe967da Compare August 12, 2026 11:59
@openshift-ci

openshift-ci Bot commented Aug 12, 2026

Copy link
Copy Markdown

@sriroopar: all tests passed!

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

@sriroopar

Copy link
Copy Markdown
Author

/retest

@blublinsky

Copy link
Copy Markdown
Contributor

Should-fix: Duplicate terminal-phase boilerplate in reconciler switch

The sandbox cleanup → audit cleanup → handleTerminalTTL block is copy-pasted across four terminal cases (NoActionRequired, Completed, Denied/Escalated/EmergencyStopped, Failed), differing only in the revision guard condition. A single helper like handleTerminalCleanup(ctx, run, phase) called after each guard would eliminate ~30 lines of duplication and reduce the risk of future divergence (e.g., adding a new cleanup step to one block but forgetting the others).

func (r *AgenticRunReconciler) handleTerminalCleanup(ctx context.Context, run *agenticv1alpha1.AgenticRun, phase string) (ctrl.Result, error) {
    if hasSandboxClaims(run) {
        if err := r.Agent.ReleaseSandboxes(ctx, run); err != nil {
            logf.FromContext(ctx).Error(err, "sandbox cleanup failed at terminal phase")
        }
    }
    if r.Audit != nil {
        r.Audit.EmitTerminalSpan(ctx, run, phase, terminalReason(run))
        r.Audit.Cleanup(run)
    }
    if result, requeue, err := r.handleTerminalTTL(ctx, run); requeue || err != nil {
        return result, err
    }
    return ctrl.Result{}, nil
}

Not blocking — the code is correct as-is, but this would reduce maintenance risk.

(reconciler.go lines ~110-170)

@blublinsky

Copy link
Copy Markdown
Contributor

Nice-to-have: Collapse three structurally identical regression tests into a table-driven test

TestNoActionRequired_TTLStampDoesNotReTriggerRevision, TestAdvisoryCompleted_TTLStampDoesNotReTriggerRevision, and TestExecutionlessFailed_TTLStampDoesNotReTriggerRevision in ttl_test.go follow the exact same pattern — only the fixture conditions, TTL values, and phase differ. These could be collapsed into a single table-driven test, reducing ~250 lines to ~80.


Nice-to-have: omitzero vs omitempty inconsistency

Lifecycle LifecycleConfig in agenticolsconfig_types.go:66 uses json:"lifecycle,omitzero" while every other field in the codebase uses omitempty. omitzero is valid in Go 1.24+ with encoding/json/v2, but if downstream consumers use encoding/json v1, the tag is silently ignored and the field will always be serialized (even when zero-valued). Worth aligning with the rest of the codebase for consistency.

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