Skip to content

RHOAIENG-48052: Scheduled sessions#864

Merged
mergify[bot] merged 4 commits intomainfrom
scheduled-sessions
Mar 12, 2026
Merged

RHOAIENG-48052: Scheduled sessions#864
mergify[bot] merged 4 commits intomainfrom
scheduled-sessions

Conversation

@mprpic
Copy link
Contributor

@mprpic mprpic commented Mar 10, 2026

Introduce scheduled sessions that let users create cron-based schedules
for automatically launching agentic sessions. The feature spans all
layers of the platform:

Backend: CRUD handlers for scheduled sessions backed by Kubernetes
CronJobs, with suspend/resume, manual trigger, and run-history
endpoints. Auth checks delegate to the user's token while CronJob
operations use the backend service account.

Operator: session-trigger subcommand that runs inside CronJob-spawned
pods, reads a session template from env vars, and creates an
AgenticSession CR. ProjectSettings reconciler ensures per-namespace
RBAC (ServiceAccount, Role, RoleBinding) for the trigger SA.

Frontend: Schedules tab in the project workspace, create dialog with
cron presets and expression preview, detail page with run history,
Next.js API route proxies, React Query hooks, and TypeScript types.

Manifests: Backend ClusterRole gains CronJob and Job/create permissions,
operator ClusterRole gains AgenticSession/create, backend deployment
reads OPERATOR_IMAGE and IMAGE_PULL_POLICY from operator-config
ConfigMap, Vertex AI config keys made optional to avoid clobbering.

Includes unit tests for backend helpers (sanitizeLabelValue,
cronJobToScheduledSession), operator trigger (sanitizeName), operator
RBAC (ensureSessionTriggerRBAC), frontend query hooks, and cron utils.

@github-actions

This comment has been minimized.

@github-actions
Copy link
Contributor

Claude Code Review

Summary

This PR introduces scheduled sessions backed by Kubernetes CronJobs, spanning backend CRUD handlers, an operator session-trigger subcommand, per-namespace RBAC provisioning, and a full frontend UI with React Query hooks. The feature is architecturally sound and includes impressive test coverage. However, the backend handlers deviate from the established security pattern for K8s client usage, and there are a few type-safety and error-handling issues to address.


Issues by Severity

Blocker Issues

None.


Critical Issues

1. Mutating handlers bypass resource-specific RBAC checks

components/backend/handlers/scheduled_sessions.goCreateScheduledSession (L128), UpdateScheduledSession (L256), DeleteScheduledSession (L328), patchSuspend (L365), TriggerScheduledSession (L398)

The handlers verify token validity and rely on ValidateProjectContext for namespace-level access, then call K8sClientScheduled (service account) directly for all writes. There is no SelfSubjectAccessReview for CronJob operations.

This means any user who can get the namespace can create, modify, and delete scheduled sessions regardless of their actual project role. Per k8s-client-usage.md and security-standards.md, an RBAC check is required before using the service account for writes. Since CronJobs are platform-managed, a suitable proxy check is the agenticsessions create/update/delete verb — the same gate used by the main session handlers:

ssar := &authv1.SelfSubjectAccessReview{
    Spec: authv1.SelfSubjectAccessReviewSpec{
        ResourceAttributes: &authv1.ResourceAttributes{
            Group:     "vteam.ambient-code",
            Resource:  "agenticsessions",
            Verb:      "create",
            Namespace: projectName,
        },
    },
}
res, err := reqK8s.AuthorizationV1().SelfSubjectAccessReviews().Create(ctx, ssar, v1.CreateOptions{})
if err != nil || !res.Status.Allowed {
    c.JSON(http.StatusForbidden, gin.H{"error": "Unauthorized to manage scheduled sessions"})
    return
}

Major Issues

1. Type mismatch: TriggerScheduledSession backend response vs. frontend TypeScript type

Backend (scheduled_sessions.go:448) returns {"name": ..., "namespace": ...}.
Frontend type (services/api/scheduled-sessions.ts:80) declares Promise<{ message: string }>.

Either align the backend to include a message field, or update the TypeScript type to {name: string; namespace: string}. As written, the return value is silently mistyped.


2. Internal parse errors exposed to API clients

scheduled_sessions.go:139 and scheduled_sessions.go:268:

// Current - leaks binding internals to the caller
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("Invalid request body: %v", err)})

// Correct pattern per error-handling.md
log.Printf("Failed to bind scheduled session request in project %s: %v", project, err)
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"})

3. ensureSessionTriggerRBAC errors silently dropped — no status reflection

components/operator/internal/handlers/projectsettings.go:144:

if err := ensureSessionTriggerRBAC(namespace); err != nil {
    log.Printf("Error ensuring session trigger RBAC in namespace %s: %v", namespace, err)
    // error discarded — reconciliation continues
}

Per error-handling.md, silent failures hide bugs. A failure here means CronJob pods cannot create AgenticSession CRs, silently breaking all scheduled sessions in the namespace. The ProjectSettings status should reflect this with an error condition (similar to how groupBindingsCreated is tracked).


4. create-scheduled-session-dialog.tsx placed in shared components/ rather than colocated

components/frontend/src/components/create-scheduled-session-dialog.tsx is only used by workspace-sections/scheduled-sessions-tab.tsx. Per frontend-development.md:

FORBIDDEN: Creating components in shared directories if only used once
REQUIRED: Keep page-specific components with their pages

Move to components/workspace-sections/_components/create-scheduled-session-dialog.tsx.


Minor Issues

1. RBAC resources in ensureSessionTriggerRBAC lack OwnerReferences

components/operator/internal/handlers/projectsettings.go:216

The ServiceAccount, Role, and RoleBinding have no OwnerReference pointing to the ProjectSettings CR. They will be orphaned if the namespace or CR is deleted. Per CLAUDE.md: "OwnerReferences on all child resources."


2. ListScheduledSessions and GetScheduledSession use service account for read operations

scheduled_sessions.go:98-124 and 231-253 use K8sClientScheduled rather than user-scoped reqDyn. Per k8s-client-usage.md, list/get should use the user token. If intentional (users lack direct CronJob RBAC), add a comment documenting the deliberate deviation to prevent incorrect replication.


Positive Highlights

  • Security context on trigger pods: AllowPrivilegeEscalation: false and Capabilities.Drop: ALL correctly set on the trigger container (scheduled_sessions.go:205), matching security-standards.md.

  • OwnerReference on manually-triggered Jobs: TriggerScheduledSession properly sets a controller OwnerReference on the one-off Job pointing to its parent CronJob (scheduled_sessions.go:428).

  • Comprehensive test coverage: Backend helpers (sanitizeLabelValue, cronJobToScheduledSession, isValidCronExpression), operator RBAC (ensureSessionTriggerRBAC), trigger name sanitization, and frontend React Query hooks all have focused, well-structured tests. The Ginkgo test-per-scenario style is excellent.

  • Idiomatic React Query: scheduledSessionKeys factory follows the recommended hierarchical structure; mutations correctly invalidate dependent queries; enabled: !!projectName guards are in place throughout.

  • TypeScript type discipline: All new types use type (not interface). No any types in new code.

  • Defensive cron validation on both sides: isValidCronExpression (backend, with tests) rejects injection attempts; getCronDescription/getNextRuns (frontend) fail gracefully.

  • Making Vertex AI ConfigMap keys optional: true prevents kind-rebuild from clobbering a live Vertex configuration. The Makefile re-application logic is a clean solution to the same root cause.


Recommendations

  1. (Critical) Add SelfSubjectAccessReview checks for agenticsessions create/update/delete verbs before calling K8sClientScheduled in all mutating handlers.

  2. (Major) Fix the TriggerScheduledSession response shape to align with the frontend TypeScript type.

  3. (Major) Replace fmt.Sprintf("Invalid request body: %v", err) user-facing messages with generic strings; log details server-side.

  4. (Major) Propagate ensureSessionTriggerRBAC errors into ProjectSettings status.

  5. (Major) Move create-scheduled-session-dialog.tsx to be colocated with workspace-sections/.

  6. (Minor) Add OwnerReferences to the SA, Role, and RoleBinding in ensureSessionTriggerRBAC.

  7. (Minor) Add a comment in the read handlers explaining the deliberate deviation from the user-token-for-reads pattern.


Generated with Claude Code using amber.review

@mprpic mprpic force-pushed the scheduled-sessions branch from 4030cb1 to 2b65a85 Compare March 10, 2026 01:55
@github-actions
Copy link
Contributor

Claude Code Review

Summary

PR 864 introduces a well-structured scheduled sessions feature spanning all platform layers: backend CronJob CRUD handlers, an operator session-trigger subcommand, React Query hooks and UI components, RBAC manifests, and a suite of unit tests. The architecture is sound and the layered test coverage is commendable. However, there are two pre-merge correctness issues (a backend/frontend type contract mismatch and a missing resource-level RBAC check) plus several quality issues that should be addressed.


Issues by Severity

Blocker Issues

None.


Critical Issues

1. Missing resource-level RBAC check on all write handlers

Files: components/backend/handlers/scheduled_sessions.goCreateScheduledSession (L128), UpdateScheduledSession (L255), DeleteScheduledSession (L327), SuspendScheduledSession/ResumeScheduledSession (L365), TriggerScheduledSession (L397)

Problem: Every handler verifies the user has a valid token (GetK8sClientsForRequest) but then delegates all CronJob/Job operations to K8sClientScheduled (the backend service account). The ValidateProjectContext middleware only checks get on namespaces. There is no SelfSubjectAccessReview confirming the user has permission to create/update/delete CronJobs. Any namespace-accessible user can create, modify, or delete scheduled sessions regardless of their actual CronJob RBAC, because the service account performs the write.

Standard violated: k8s-client-usage.md Pattern 2 ("Validate Then Escalate"), security-standards.md "RBAC Enforcement"

Suggested fix — add a SelfSubjectAccessReview before each mutating K8s call, as is done for agenticsessions in sessions.go:

ssar := &authv1.SelfSubjectAccessReview{
    Spec: authv1.SelfSubjectAccessReviewSpec{
        ResourceAttributes: &authv1.ResourceAttributes{
            Group:     "batch",
            Resource:  "cronjobs",
            Verb:      "create",
            Namespace: project,
        },
    },
}
res, err := k8s.AuthorizationV1().SelfSubjectAccessReviews().Create(c.Request.Context(), ssar, metav1.CreateOptions{})
if err != nil || !res.Status.Allowed {
    c.JSON(http.StatusForbidden, gin.H{"error": "You do not have permission to create scheduled sessions in this project"})
    return
}

2. displayName required by backend but optional/omitted by frontend

Files: components/backend/types/scheduled_session.go L117, components/frontend/src/types/api/scheduled-sessions.ts L22, components/frontend/src/components/create-scheduled-session-dialog.tsx onSubmit

Problem: The backend struct has DisplayName string json:"displayName" binding:"required". The TypeScript type marks it displayName?: string and the dialog form schema marks it .optional(). When the user submits without a display name, the form sends displayName: undefined, which JSON serialization omits entirely. The backend returns 400 with a cryptic validation error.

Fix (pick one): Remove binding:"required" from DisplayName in the backend type, OR make the frontend form field required (displayName: z.string().min(1, "Display name is required")).


Major Issues

3. Trigger API response type mismatch

File: components/frontend/src/services/api/scheduled-sessions.ts L84

Problem: triggerScheduledSession is typed to return Promise<{ message: string }>, but the backend returns {"name": created.Name, "namespace": created.Namespace}. There is no message field; any UI code consuming it will silently get undefined.

Fix:

export async function triggerScheduledSession(
  projectName: string,
  name: string
): Promise<{ name: string; namespace: string }> { ... }

4. Gin binding error details leaked to API response

File: components/backend/handlers/scheduled_sessions.go L139, L268

Problem: fmt.Sprintf("Invalid request body: %v", err) returns Gin validation internals to the client. Per the error-handling pattern, internal errors should be logged server-side and only a generic message returned.

Standard violated: error-handling.md Anti-Pattern "Exposing Internal Errors to Users"

Fix:

log.Printf("Invalid request body for scheduled session in project %s: %v", project, err)
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"})

5. Native confirm() used for delete — violates Shadcn-only UI rule

File: components/frontend/src/components/workspace-sections/scheduled-sessions-tab.tsx L80

Problem: if (!confirm(...)) return; uses the native browser dialog, which is unstyled, blocks the JS thread, and is forbidden in this codebase.

Standard violated: frontend-development.md "Shadcn UI Components Only"

Fix: Replace with a Shadcn AlertDialog with Confirm/Cancel actions.


6. Missing initial loading and error states in SchedulesSection

File: components/frontend/src/components/workspace-sections/scheduled-sessions-tab.tsx L32

Problem: isLoading and error are not destructured from useScheduledSessions. On first render the component shows an empty state indistinguishable from "no sessions exist." On API failure the error is silently swallowed.

Standard violated: frontend-development.md Pre-Commit Checklist "Loading and error states handled"

Fix:

const { data: scheduledSessions, isFetching, isLoading, error, refetch } = useScheduledSessions(projectName);
if (isLoading) return <div className="flex justify-center p-8"><Loader2 className="animate-spin" /></div>;
if (error) return <div className="text-destructive p-4">Failed to load scheduled sessions</div>;

Minor Issues

7. Unvalidated URL parameter used in a Kubernetes label selector

File: components/backend/handlers/scheduled_sessions.go L471

Problem: labelScheduledSessionName + "=" + name where name is c.Param("scheduledSessionName") with no format validation. A malformed value (containing = or ,) could be misinterpreted by the label selector parser.

Fix: Validate name against a K8s DNS label regex before using it in the selector.


8. context.TODO() in trigger.go

File: components/operator/internal/trigger/trigger.go L82

Problem: context.TODO() in production code lacks timeout/cancellation support.

Fix:

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
_, err = dynamicClient.Resource(gvr).Namespace(projectNamespace).Create(ctx, session, metav1.CreateOptions{})

9. Redundant K8sClientScheduled package-level variable

Files: components/backend/handlers/scheduled_sessions.go L88, components/backend/main.go L1037

Problem: K8sClientScheduled is always assigned server.K8sClient. There is no scenario where they differ, yet a new global must be initialized and kept in sync.

Fix: Remove K8sClientScheduled and use the existing K8sClient (already package-level in handlers/) directly.


10. create-scheduled-session-dialog.tsx in shared components/ instead of colocated

File: components/frontend/src/components/create-scheduled-session-dialog.tsx

Problem: This dialog is only used by the scheduled sessions feature.

Standard violated: frontend-development.md "Colocate Single-Use Components"

Fix: Move to src/app/projects/[name]/scheduled-sessions/_components/.


Positive Highlights

  • Excellent test coverage across all layers: Backend tests for sanitizeLabelValue, cronJobToScheduledSession, and isValidCronExpression cover edge cases thoroughly (truncation, non-alphanumeric trimming, injection via special characters); operator sanitizeName tests are equally rigorous; the React Query hook tests mock the full API surface and cover all mutations.
  • Correct SecurityContext on trigger containers: AllowPrivilegeEscalation: false and dropping all capabilities is set correctly per the container security standard.
  • OwnerReference on manual trigger Jobs: TriggerScheduledSession correctly sets a controller OwnerReference pointing to the CronJob so manually triggered Jobs are garbage-collected with the CronJob.
  • Idempotent RBAC provisioning: ensureSessionTriggerRBAC correctly handles errors.IsAlreadyExists to make reconciliation safe to re-run.
  • Cron injection protection: isValidCronExpression rejects ;, backtick, and other shell-special characters before the expression reaches Kubernetes.
  • Configurable operator image and pull policy: Reading from the operator-config ConfigMap with optional: true is a clean pattern that avoids breaking non-Vertex deployments.
  • Well-structured React Query key factory: scheduledSessionKeys with typed hierarchical keys enables precise and predictable cache invalidation.

Recommendations (priority order)

  1. [Critical] Add SelfSubjectAccessReview for create/update/delete verbs on batch/cronjobs in all mutating handlers — required by the security architecture.
  2. [Critical] Reconcile displayName optionality between the backend binding tag and the frontend type/form.
  3. [Major] Fix the trigger response type ({name, namespace} vs {message}).
  4. [Major] Replace confirm() with Shadcn AlertDialog for delete confirmation.
  5. [Major] Add isLoading/error guards in SchedulesSection.
  6. [Minor] Switch context.TODO() to context.Background() with a timeout in trigger.go.
  7. [Minor] Remove K8sClientScheduled and use the existing K8sClient directly.

mprpic added a commit that referenced this pull request Mar 10, 2026
- Add SelfSubjectAccessReview RBAC checks to all scheduled session
  handlers (list, get, create, update, delete, suspend/resume, trigger)
- Make displayName optional in CreateScheduledSessionRequest
- Fix trigger API response type mismatch (name/namespace vs message)
- Stop leaking Gin binding errors to API responses
- Propagate ensureSessionTriggerRBAC errors into ProjectSettings status
- Add OwnerReferences to SA/Role/RoleBinding created by trigger RBAC
- Add loading and error states to SchedulesSection component
- Fix sanitizeName trailing-hyphen bug (truncate before trim)
- Move create-scheduled-session-dialog to colocated location

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Martin Prpič <mprpic@redhat.com>
@coderabbitai
Copy link

coderabbitai bot commented Mar 10, 2026

Walkthrough

This PR introduces a complete "scheduled sessions" feature enabling users to create, manage, and trigger recurring session jobs using Kubernetes CronJobs. It includes backend handlers for CRUD operations, frontend UI components for management, API routes, React Query hooks, RBAC provisioning, and operator trigger functionality.

Changes

Cohort / File(s) Summary
Build & Configuration
Makefile, components/backend/server/k8s.go, components/manifests/overlays/.../operator-config.yaml
Added Vertex AI re-application in kind-rebuild flow; added OPERATOR_IMAGE and IMAGE_PULL_POLICY environment variables with defaults; updated ConfigMap overlays to include new vars and remove hardcoded Vertex config.
Backend Handlers – Scheduled Sessions CRUD
components/backend/handlers/scheduled_sessions.go, components/backend/handlers/scheduled_sessions_test.go
Implemented comprehensive CRUD handler for scheduled sessions backed by Kubernetes CronJobs, including conversion from CronJob to ScheduledSession type, label/cron validation helpers, and 387 lines of test coverage for conversion logic and helper functions.
Backend Handlers – Initialization & Tests
components/backend/handlers/test_helpers_test.go, components/backend/handlers/sessions_test.go, components/backend/main.go
Initialized K8sClientScheduled global variable in test setup and main server initialization; updated existing session tests to handle both current and deprecated Vertex environment variables.
Backend Routes & Types
components/backend/routes.go, components/backend/types/scheduled_session.go
Added 11 new REST endpoints for scheduled sessions (list, create, get, update, delete, suspend, resume, trigger, list runs); defined three types (CreateScheduledSessionRequest, UpdateScheduledSessionRequest, ScheduledSession) with JSON bindings.
Frontend API Routes
components/frontend/src/app/api/projects/[name]/scheduled-sessions/*
Implemented 6 new proxy route handlers (GET/POST for list/create, GET/PUT/DELETE for detail, POST for suspend/resume/trigger, GET for runs) forwarding to backend endpoints with proper error handling and header management.
Frontend Services & Hooks
components/frontend/src/services/api/scheduled-sessions.ts, components/frontend/src/services/queries/use-scheduled-sessions.ts, components/frontend/src/services/queries/__tests__/use-scheduled-sessions.test.ts
Created API service layer with 9 functions, React Query hooks with cache-key management and mutation invalidation strategies, and comprehensive test suite with 272 lines of test coverage.
Frontend UI Components
components/frontend/src/components/workspace-sections/scheduled-sessions-tab.tsx, components/frontend/src/components/workspace-sections/create-scheduled-session-dialog.tsx
Built SchedulesSection with listing/actions table and CreateScheduledSessionDialog with form validation (zod), cron presets, live cron descriptions, and loading/error states; 601 combined lines.
Frontend Detail Pages & Sub-Components
components/frontend/src/app/projects/[name]/scheduled-sessions/[scheduledSessionName]/page.tsx, components/frontend/src/app/projects/[name]/scheduled-sessions/[scheduledSessionName]/_components/*.tsx
Created detail page with data fetching, mutation handlers, and two sub-components: ScheduledSessionDetailsCard (metadata display) and ScheduledSessionRunsTable (runs listing with relative timestamps).
Frontend Utilities & Types
components/frontend/src/lib/cron.ts, components/frontend/src/lib/__tests__/cron.test.ts, components/frontend/src/types/api/scheduled-sessions.ts, components/frontend/package.json
Added cron utilities (getCronDescription, getNextRuns) with graceful error handling and test coverage; defined TypeScript API types mirroring backend; added cron-parser and cronstrue dependencies.
Frontend Navigation
components/frontend/src/app/projects/[name]/page.tsx, components/frontend/src/components/chat/AttachmentPreview.tsx, components/frontend/src/components/jira-connection-card.tsx
Extended project page to include "Schedules" tab with Calendar icon; minor ESLint directive and dependency array updates.
Kubernetes RBAC & Manifests
components/manifests/base/rbac/backend-clusterrole.yaml, components/manifests/base/rbac/operator-clusterrole.yaml, components/manifests/base/backend-deployment.yaml, components/manifests/base/crds/projectsettings-crd.yaml
Expanded Jobs and added CronJobs RBAC rules; enabled create verb for agenticsessions; made environment variables optional in deployment specs; added scheduledSessionRBACReady status field to ProjectSettings CRD.
Operator – Session Trigger
components/operator/internal/trigger/trigger.go, components/operator/internal/trigger/trigger_test.go, components/operator/main.go
Implemented session-trigger subcommand creating AgenticSession resources from CronJob templates; added sanitizeName helper with 90 lines of test coverage; integrated trigger entry point in main.
Operator – RBAC Provisioning
components/operator/internal/handlers/projectsettings.go, components/operator/internal/handlers/projectsettings_test.go
Added ensureSessionTriggerRBAC function idempotently provisioning ServiceAccount, Role, and RoleBinding for scheduled session triggers; integrated into ProjectSettings reconciliation with status tracking and 201 lines of test coverage.
Scripts
scripts/setup-vertex-kind.sh, components/public-api/handlers/middleware.go, components/public-api/main.go
Optimized Vertex setup script rollout restarts; minor formatting adjustments in public API files.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Frontend
    participant FrontendAPI as Frontend<br/>API Routes
    participant BackendAPI as Backend<br/>Handlers
    participant K8s as Kubernetes<br/>API
    participant Operator as Operator<br/>Trigger
    
    User->>Frontend: Create scheduled session
    Frontend->>Frontend: Validate cron expression
    Frontend->>FrontendAPI: POST /scheduled-sessions
    FrontendAPI->>BackendAPI: POST create (forwarded)
    BackendAPI->>K8s: Create CronJob<br/>(with SESSION_TEMPLATE env)
    K8s-->>BackendAPI: CronJob created
    BackendAPI->>K8s: Convert to ScheduledSession
    BackendAPI-->>FrontendAPI: Return ScheduledSession
    FrontendAPI-->>Frontend: Response
    Frontend-->>User: Show created session
    
    User->>Frontend: Trigger now
    Frontend->>FrontendAPI: POST /trigger
    FrontendAPI->>BackendAPI: POST trigger (forwarded)
    BackendAPI->>K8s: Create Job from CronJob<br/>JobTemplate
    K8s-->>BackendAPI: Job created
    BackendAPI->>K8s: Create AgenticSession<br/>(from template)
    K8s->>Operator: CronJob controller<br/>sees new Job
    Operator->>Operator: session-trigger<br/>reads env vars
    Operator->>K8s: Create AgenticSession<br/>with displayName
    K8s-->>BackendAPI: AgenticSession created
    BackendAPI-->>FrontendAPI: Return job details
    FrontendAPI-->>Frontend: Response
    Frontend-->>User: Show triggered run
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 34.21% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'RHOAIENG-48052: Scheduled sessions' directly references the main feature being implemented and is concise and clear.
Description check ✅ Passed The description comprehensively covers the feature implementation across all layers (backend, operator, frontend, manifests) with specific details about CRUD handlers, authorization, CronJob operations, session templates, RBAC provisioning, and testing.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch scheduled-sessions

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

mprpic added a commit that referenced this pull request Mar 10, 2026
- Add SelfSubjectAccessReview RBAC checks to all scheduled session
  handlers (list, get, create, update, delete, suspend/resume, trigger)
- Make displayName optional in CreateScheduledSessionRequest
- Fix trigger API response type mismatch (name/namespace vs message)
- Stop leaking Gin binding errors to API responses
- Propagate ensureSessionTriggerRBAC errors into ProjectSettings status
- Add OwnerReferences to SA/Role/RoleBinding created by trigger RBAC
- Add loading and error states to SchedulesSection component
- Fix sanitizeName trailing-hyphen bug (truncate before trim)
- Move create-scheduled-session-dialog to colocated location

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Martin Prpič <mprpic@redhat.com>
@mprpic mprpic force-pushed the scheduled-sessions branch from da257a4 to 7b9f763 Compare March 10, 2026 14:35
@github-actions
Copy link
Contributor

github-actions bot commented Mar 10, 2026

Claude Code Review

Summary

This PR introduces a well-structured scheduled sessions feature spanning all platform layers (backend CronJob CRUD, operator RBAC provisioning, frontend React Query + UI). The overall architecture is sound: user-token RBAC checks are correctly gated before service-account CronJob operations, and the frontend follows React Query and Shadcn patterns. A few issues need attention before merge—the most important are error-wrapping violations in the operator, RBAC role drift in the new reconciler, and native confirm() dialogs in the frontend.


Issues by Severity

Blocker Issues

None.

Critical Issues

None.

Major Issues

1. Error wrapping uses %v instead of %w in ensureSessionTriggerRBAC

  • File: components/operator/internal/handlers/projectsettings.go (SA ~line 263, Role ~line 283, RoleBinding ~line 305)
  • Problem: All three fmt.Errorf calls use %v for the wrapped error, breaking the error chain and preventing errors.Is/errors.As introspection upstream.
  • Violates: error-handling.md"Errors wrapped with fmt.Errorf("context: %w", err)"
  • Fix:
    // Before
    return fmt.Errorf("failed to create ServiceAccount %s: %v", saName, err)
    // After
    return fmt.Errorf("failed to create ServiceAccount %s: %w", saName, err)

2. RBAC role drift — existing Role is never updated

  • File: components/operator/internal/handlers/projectsettings.go (~line 277)
  • Problem: ensureSessionTriggerRBAC only calls Create and ignores AlreadyExists. If the Role's PolicyRule entries are expanded in a future release, existing clusters will keep the old (under-privileged) role indefinitely, silently breaking scheduled sessions.
  • Violates: Operator reconciliation should be idempotent and convergent.
  • Fix: After the AlreadyExists branch, call Update to overwrite the rules:
    if _, err := config.K8sClient.RbacV1().Roles(namespace).Create(ctx, role, v1.CreateOptions{}); err != nil {
        if !errors.IsAlreadyExists(err) {
            return fmt.Errorf("failed to create Role %s: %w", roleName, err)
        }
        if _, err := config.K8sClient.RbacV1().Roles(namespace).Update(ctx, role, v1.UpdateOptions{}); err != nil {
            return fmt.Errorf("failed to update Role %s: %w", roleName, err)
        }
    }

3. window.confirm() used for destructive delete

  • Files:
    • components/frontend/src/components/workspace-sections/scheduled-sessions-tab.tsx (~line 83)
    • components/frontend/src/app/projects/[name]/scheduled-sessions/[scheduledSessionName]/page.tsx (~line 100)
  • Problem: if (!confirm(...)) return; — native browser dialogs block the render thread and are not accessible.
  • Violates: frontend-development.md"Shadcn UI Components Only. FORBIDDEN: Creating custom UI components from scratch for buttons, inputs, dialogs, etc."
  • Fix: Replace with Shadcn AlertDialog (same pattern used in session deletion elsewhere in the codebase).

4. context.TODO() in operator reconcile path

  • File: components/operator/internal/handlers/projectsettings.go (~lines 258, 278, 299)
  • Problem: ensureSessionTriggerRBAC calls config.K8sClient.*.Create(context.TODO(), ...) instead of propagating the reconcile context.
  • Violates: backend-development.md — propagate context for cancellation / timeout.
  • Fix: Add ctx context.Context to ensureSessionTriggerRBAC's signature and thread it through all K8s calls.

Minor Issues

5. Silent error swallowing in cronJobToScheduledSession

  • File: components/backend/handlers/scheduled_sessions.go (~line 542)
  • Problem:
    if err := json.Unmarshal([]byte(env.Value), &tmpl); err == nil {
        ss.SessionTemplate = tmpl
    }
    If SESSION_TEMPLATE is malformed, the field is silently zeroed with no log entry.
  • Violates: error-handling.md"Silent failures: NEVER DO THIS".
  • Fix: Add else { log.Printf("cronJobToScheduledSession: failed to unmarshal SESSION_TEMPLATE: %v", err) }.

6. ListScheduledSessionRuns auth check deviates from standard pattern

  • File: components/backend/handlers/scheduled_sessions.go (~line 484)
  • Problem: Checks k8sDyn == nil instead of the project-standard reqK8s == nil.
  • Violates: backend-development.md"if reqK8s == nil { ... }"
  • Fix:
    reqK8s, k8sDyn := GetK8sClientsForRequest(c)
    if reqK8s == nil {

7. Hardcoded temperature and maxTokens in create dialog

  • File: components/frontend/src/components/workspace-sections/create-scheduled-session-dialog.tsx (~line 155)
  • Problem: llmSettings: { model: values.model, temperature: 0.7, maxTokens: 4000 } — temperature and token limit are hard-coded and will drift from platform defaults.
  • Fix: Either expose them as form fields or derive from the model API response if defaults are available there.

8. SESSION_TEMPLATE content stored in plaintext CronJob env var

  • File: components/backend/handlers/scheduled_sessions.go (~line 235)
  • Problem: The session template JSON is visible to any principal who can kubectl get cronjob -o yaml in the namespace.
  • Suggestion: Document this limitation; if confidentiality is required, store the template in a Secret and reference it via valueFrom.secretKeyRef.

Positive Highlights

  • RBAC validation pattern is correct throughout: checkScheduledSessionAccess consistently uses GetK8sClientsForRequest for user-token RBAC checks before all service-account CronJob operations — exactly the "validate then escalate" pattern required by k8s-client-usage.md.
  • SecurityContext on trigger container: AllowPrivilegeEscalation: false and Capabilities.Drop: ALL correctly set per security standards.
  • OwnerReferences on manual-trigger Jobs: TriggerScheduledSession correctly sets an OwnerReference back to the parent CronJob, ensuring cleanup propagation.
  • React Query implementation is exemplary: scheduledSessionKeys factory, enabled guards, keepPreviousData, and full cache invalidation on mutations all follow the documented patterns from react-query-usage.md precisely.
  • TypeScript types use type throughout: All new frontend types correctly use type instead of interface.
  • Test coverage is comprehensive: Backend helpers (sanitizeLabelValue, cronJobToScheduledSession, isValidCronExpression), operator RBAC provisioning, trigger sanitizeName, and all React Query hooks have dedicated test files.
  • Generic user error messages with detailed server logs: New backend handlers consistently log with context while returning opaque error strings to the client.
  • Vertex AI config keys made optional: Adding optional: true prevents deployment crashes on clusters without Vertex configured.

Recommendations

  1. (Major, easy) Fix %v%w in all three ensureSessionTriggerRBAC error returns.
  2. (Major) Add an Update call for the Role in ensureSessionTriggerRBAC to prevent rule drift on long-lived clusters.
  3. (Major) Replace both confirm() delete dialogs with a Shadcn AlertDialog.
  4. (Major) Thread real context into ensureSessionTriggerRBAC instead of context.TODO().
  5. (Minor) Log json.Unmarshal errors in cronJobToScheduledSession.
  6. (Minor) Standardise ListScheduledSessionRuns to check reqK8s == nil.

🤖 Generated with Claude Code


🔍 View AI decision process (logs available for 90 days)

📋 View memory system files loaded (click to expand)

What Amber Loaded for Code Review

Amber automatically loaded these repository standards from the memory system:

  1. CLAUDE.md - Master project instructions, development standards
  2. backend-development.md - Go backend, K8s integration patterns
  3. frontend-development.md - NextJS, Shadcn UI, React Query patterns
  4. security-standards.md - Auth, RBAC, token handling
  5. k8s-client-usage.md - User token vs service account patterns
  6. error-handling.md - Consistent error patterns
  7. react-query-usage.md - Data fetching patterns

Impact: This review used your repository's specific code quality standards, security patterns, and best practices from the memory system (PRs #359, #360) - not just generic code review guidelines.

@github-actions
Copy link
Contributor

Claude Code Review

Summary

This PR introduces a well-architected scheduled sessions feature spanning all platform layers — backend CronJob CRUD, operator trigger subcommand with per-namespace RBAC provisioning, and a complete frontend with React Query hooks, Shadcn UI, and thorough unit tests. The security model is sound: user token auth gates every operation via RBAC proxy on agenticsessions, and service account clients are used only for platform-managed CronJob resources. No blocker or critical issues found.

Blocker Issues

None

Critical Issues

None

Major Issues

None

Minor Issues

1. ListScheduledSessionRuns checks k8sDyn == nil instead of k8s == nil
components/backend/handlers/scheduled_sessions.go:484

All other handlers (and the k8s-client-usage.md pattern) check the first return value (reqK8s == nil). If GetK8sClientsForRequest ever returns (nil, non-nil) on auth failure, this check would pass silently. Align with the project-wide pattern:

reqK8s, reqDyn := GetK8sClientsForRequest(c)
if reqK8s == nil {
    c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid or missing token"})
    c.Abort()
    return
}

2. ListScheduledSessionRuns skips the explicit RBAC gate
components/backend/handlers/scheduled_sessions.go:480-505

Every other handler in this file calls checkScheduledSessionAccess(c, verb) before operating. ListScheduledSessionRuns only checks for a valid token, relying on implicit K8s RBAC enforcement through the user dynamic client. While functionally safe, the inconsistency makes the code harder to audit. Consider adding checkScheduledSessionAccess(c, "list") for consistency.

3. Missing log statement before marshal error in UpdateScheduledSession
components/backend/handlers/scheduled_sessions.go:332-335

Per error-handling.md, errors should be logged with context before returning a generic user message. The current code silently swallows the marshalling error. Add:

log.Printf("Failed to marshal session template for update %s in project %s: %v", name, project, err)

4. projectName not URL-encoded in Link href in scheduled-sessions-tab.tsx

The table row Link uses bare ${projectName} while adjacent router.push calls in page.tsx correctly use encodeURIComponent(projectName). Project names with special characters could produce broken links. Use encodeURIComponent for both the project name and session name segments.

5. Delete confirmation uses window.confirm() instead of a Shadcn dialog

Both scheduled-sessions-tab.tsx and the detail page.tsx use the native confirm() call for destructive actions. frontend-development.md requires Shadcn UI components for all UI interactions. Consider using AlertDialog from @/components/ui/alert-dialog.

6. context.TODO() in ensureSessionTriggerRBAC
components/operator/internal/handlers/projectsettings.go

All three K8s calls in this function use context.TODO(). Consider plumbing a context with timeout from the reconciliation entry point, consistent with how other operator handlers use context.WithTimeout. Low risk since the reconciler handles retries, but it improves predictability.

7. as any cast in test file
components/frontend/src/services/queries/__tests__/use-scheduled-sessions.test.ts

The as any cast with eslint-disable suppresses a type error in the useCreateScheduledSession test. A minimal but properly-typed CreateScheduledSessionRequest fixture would be more idiomatic and serve as documentation of the required shape.

8. ReadOnlyRootFilesystem not set on trigger container
components/backend/handlers/scheduled_sessions.go (trigger container SecurityContext)

The trigger binary only reads env vars and makes a K8s API call — it writes nothing to disk. Setting ReadOnlyRootFilesystem: types.BoolPtr(true) would harden the container further, consistent with the Drop: ALL security posture already in place.

Positive Highlights

  • Security model is solid: The RBAC proxy pattern (checkScheduledSessionAccess using agenticsessions as a permission gate for CronJob operations) is clearly commented, correctly implemented, and consistently applied across all CRUD operations. The separation between user-token clients for auth checks and service account clients for CronJob operations follows the established project pattern exactly.

  • Comprehensive test coverage: Unit tests for sanitizeLabelValue, isValidCronExpression, cronJobToScheduledSession (backend), sanitizeName (trigger), ensureSessionTriggerRBAC (operator with idempotency and multi-namespace scenarios), all React Query hooks, and cron utility functions. The test structure mirrors existing patterns well.

  • OwnerReferences correctly set: The manually triggered Job correctly sets the CronJob as the controller owner reference for automatic GC. The SA/Role/RoleBinding created by the operator correctly reference the ProjectSettings owner.

  • Frontend architecture is clean: Proper separation between services/api/ (pure functions) and services/queries/ (React Query hooks), correct query key factory pattern (scheduledSessionKeys), complete cache invalidation in all mutations, and all types use type over interface. Shadcn components used throughout.

  • Input validation: isValidCronExpression provides structural validation client-side and server-side. sanitizeLabelValue correctly sanitizes user IDs for K8s labels with truncation and boundary enforcement. Log injection is prevented by not logging user-controlled values directly.

Recommendations (priority order)

  1. Fix k8sDyn == nilreqK8s == nil in ListScheduledSessionRuns (pattern alignment, prevents silent auth bypass if return signature ever diverges).
  2. Add encodeURIComponent to the Link href in scheduled-sessions-tab.tsx (correctness for project names with special characters).
  3. Add explicit RBAC check in ListScheduledSessionRuns (consistency, auditability).
  4. Add missing log.Printf before the marshal error in UpdateScheduledSession (observability).
  5. Replace window.confirm() with AlertDialog in delete handlers (Shadcn UI consistency).
  6. Consider ReadOnlyRootFilesystem: true on the trigger container (defense in depth).

Generated with Claude Code

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 12

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
components/frontend/src/components/jira-connection-card.tsx (1)

40-49: ⚠️ Potential issue | 🟠 Major

Adding url and username to the dependency array prevents users from clearing these fields.

With this change, if a user clears the URL or username field entirely (e.g., to paste a different value), the effect immediately re-runs and repopulates the default. The original dependencies [showForm, currentUser?.email] correctly limited this "initial population" behavior to when the form is first shown.

Proposed fix: revert to original dependencies
   }, [showForm, currentUser?.email, url, username])
+  // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [showForm, currentUser?.email])

Alternatively, use a ref to track whether initial population has occurred:

const didPopulateRef = useRef(false)

useEffect(() => {
  if (showForm && !didPopulateRef.current) {
    didPopulateRef.current = true
    if (!url) setUrl(DEFAULT_JIRA_URL)
    if (!username && currentUser?.email) setUsername(currentUser.email)
  }
  if (!showForm) {
    didPopulateRef.current = false
  }
}, [showForm, currentUser?.email, url, username])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/frontend/src/components/jira-connection-card.tsx` around lines 40
- 49, The effect currently reruns when `url` or `username` change, causing
cleared fields to be immediately repopulated; revert the dependency array on the
`useEffect` that sets `DEFAULT_JIRA_URL` and pre-fills `username` back to only
`[showForm, currentUser?.email]` so the initial population runs only when the
form is shown, or alternatively implement a `didPopulateRef` boolean to ensure
`useEffect` (the same block using `setUrl`, `setUsername`, `DEFAULT_JIRA_URL`,
and `currentUser?.email`) only populates fields once per show and resets the ref
when `showForm` becomes false.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@components/backend/handlers/scheduled_sessions.go`:
- Around line 239-292: The Get->modify->Update sequence on the CronJob
(K8sClientScheduled.BatchV1().CronJobs(...).Get and Update) can race with
concurrent writers; implement retry-on-conflict: wrap the read-modify-update
logic in a small retry loop (e.g., 3 attempts), on errors.IsConflict(err)
re-fetch the CronJob, reapply the same modifications (schedule, suspend,
annotations, SESSION_TEMPLATE env update) and call Update again, aborting on
non-conflict errors and returning the same HTTP errors/logs; preserve use of
c.Request.Context(), keep annotationDisplayName and cronJobToScheduledSession
handling intact, and log conflicts when retries are exhausted.
- Around line 266-282: The current update loop in scheduled_sessions.go only
replaces SESSION_TEMPLATE if that env var already exists in the "trigger"
container, so when it's absent the change is silently skipped; modify the logic
in the handler that processes cj.Spec.JobTemplate.Spec.Template.Spec.Containers
(the block that finds container.Name == "trigger") to track whether an env var
named "SESSION_TEMPLATE" was found and updated, and if not append a new
corev1.EnvVar (or appropriate Env type used in this file) with Name
"SESSION_TEMPLATE" and Value set to the marshaled templateJSON; ensure you
perform this add within the same container handling (e.g., after the inner loop)
so the env var is created when missing.
- Around line 415-441: Add an explicit RBAC check using the existing
checkScheduledSessionAccess helper at the start of ListScheduledSessionRuns
(before calling k8sDyn.Resource(...).List). Call checkScheduledSessionAccess(c,
project, name, "list") (or the module's actual checkScheduledSessionAccess
signature) and if it denies access return the same HTTP error handling used by
other handlers (abort with 403/unauthorized and JSON error) so the pattern
matches ListScheduledSessions/CreateScheduledSession/GetScheduledSession/etc.;
ensure this check runs before the k8s list call and references
labelScheduledSessionName/name as in the current function.

In
`@components/frontend/src/app/api/projects/`[name]/scheduled-sessions/[scheduledSessionName]/suspend/route.ts:
- Around line 15-16: The current code always calls response.text() and
reconstructs a Response, which breaks for null-body statuses (204/205/304);
update the suspend route handler to first check response.status (or use a helper
like isNoContentStatus) and if it is 204, 205, or 304 return new Response(null,
{ status: response.status, headers: { ... } }) (matching the DELETE handler
pattern), otherwise await response.text() and return the reconstructed Response
with the JSON Content-Type; reference the existing response variable, the const
text assignment, and the Response constructor when making the change.

In `@components/frontend/src/app/api/projects/`[name]/scheduled-sessions/route.ts:
- Around line 5-41: Both GET and POST duplicate proxy logic (header forwarding,
fetch to BACKEND_URL, reading text, reconstructing Response, and error
handling); extract this into a single helper (e.g., forwardProxy or
proxyScheduledSessionRequest) that accepts the incoming Request, resolved params
(await params), the target path suffix ("/scheduled-sessions"), and HTTP method,
then: build headers with buildForwardHeadersAsync(request), perform fetch to
`${BACKEND_URL}/projects/${encodeURIComponent(name)}${pathSuffix}` with the
method, pass through body when present, read response.text(), and return a new
Response preserving response.status and Content-Type: application/json; on
exceptions return Response.json({ error: ... }, { status: 500 }) and log the
error. Replace GET and POST to call this helper; reuse the same helper in the
runs, suspend, and trigger route files to remove duplication.

In
`@components/frontend/src/components/workspace-sections/create-scheduled-session-dialog.tsx`:
- Around line 169-172: The trigger wrapper currently uses a plain <div> with an
onClick (the element that calls setOpen(true) and renders trigger) which is not
keyboard-accessible; replace that clickable div with the DialogTrigger component
from your Dialog primitives (import DialogTrigger) so keyboard and ARIA
behaviors are handled, and remove the redundant fragment/closing fragment around
the Dialog; ensure Dialog still receives open and onOpenChange
(handleOpenChange) and that you pass the original trigger as DialogTrigger's
child.

In `@components/frontend/src/lib/__tests__/cron.test.ts`:
- Around line 21-29: The test is flaky because it calls Date.now() during
assertions instead of using a captured timestamp; before invoking getNextRuns('0
* * * *', 3) capture a const now = Date.now() and then use that captured now in
the assertions (replace Date.now() checks with now) to ensure comparisons
against a stable reference; update the test around the getNextRuns call and the
loop that checks date.getTime() and the ordering assertion to reference now and
keep the same checks that each entry is a Date and increasing.

In `@components/frontend/src/services/api/scheduled-sessions.ts`:
- Around line 14-21: All functions interpolate raw path parameters causing
malformed URLs; update each function (listScheduledSessions,
createScheduledSession, getScheduledSession, updateScheduledSession,
deleteScheduledSession, suspendScheduledSession, resumeScheduledSession,
triggerScheduledSession, listScheduledSessionRuns) to URL-encode path segments
by replacing direct uses of projectName and name in template strings with
encodeURIComponent(projectName) and encodeURIComponent(name) (or local consts
like encodedProject = encodeURIComponent(projectName), encodedName =
encodeURIComponent(name)) so the API paths become
`/projects/${encodedProject}/...` and
`/projects/${encodedProject}/scheduled-sessions/${encodedName}/...` consistently
across the file.

In
`@components/frontend/src/services/queries/__tests__/use-scheduled-sessions.test.ts`:
- Line 83: The mock for triggerScheduledSession in
use-scheduled-sessions.test.ts returns { message: 'triggered' } but the real API
returns an object shaped { name: string; namespace: string }; change the
vi.fn().mockResolvedValue to return a realistic object (e.g., { name: '...',
namespace: '...' }) and update the test assertion that currently checks the
message field to assert the returned name/namespace values (or their usages) so
the test matches the actual API contract for triggerScheduledSession.

In `@components/frontend/src/services/queries/use-scheduled-sessions.ts`:
- Around line 166-184: The onSuccess handler in useTriggerScheduledSession only
invalidates the runs list but not the scheduled session detail, so update the
onSuccess (in useTriggerScheduledSession) to also invalidate the detail query
(e.g., call queryClient.invalidateQueries with
scheduledSessionKeys.detail(projectName, name) or include that key alongside
scheduledSessionKeys.runs) so the UI fetches the updated
CronJob/LastScheduleTime state after a manual trigger.

In `@components/operator/internal/handlers/projectsettings.go`:
- Around line 145-162: The current flow sets triggerRBACReady=false when
ensureSessionTriggerRBAC(namespace, obj) fails but returns immediately before
persisting that into the CR status; change the control flow so that regardless
of the ensureSessionTriggerRBAC error you call
updateProjectSettingsStatus(namespace, name, statusUpdate) to persist
"scheduledSessionRBACReady": false (use the existing statusUpdate map) and then
return the original error (or wrap it) from the handler; modify the block around
ensureSessionTriggerRBAC, triggerRBACReady, statusUpdate and the return path so
updateProjectSettingsStatus is invoked on error before returning.

In `@components/operator/internal/trigger/trigger.go`:
- Around line 75-80: The Create call uses context.TODO() and can hang; replace
it with a cancellable context with a timeout (e.g., context.WithTimeout) before
calling dynamicClient.Resource(gvr).Namespace(projectNamespace).Create, use that
ctx in place of context.TODO(), defer the cancel() immediately after creation of
the ctx, and keep the existing error handling (log.Fatalf) unchanged; ensure the
surrounding function (e.g., where gvr and dynamicClient are used) imports and
uses the context package and choose a sensible timeout like 30s.

---

Outside diff comments:
In `@components/frontend/src/components/jira-connection-card.tsx`:
- Around line 40-49: The effect currently reruns when `url` or `username`
change, causing cleared fields to be immediately repopulated; revert the
dependency array on the `useEffect` that sets `DEFAULT_JIRA_URL` and pre-fills
`username` back to only `[showForm, currentUser?.email]` so the initial
population runs only when the form is shown, or alternatively implement a
`didPopulateRef` boolean to ensure `useEffect` (the same block using `setUrl`,
`setUsername`, `DEFAULT_JIRA_URL`, and `currentUser?.email`) only populates
fields once per show and resets the ref when `showForm` becomes false.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 190159fb-25ff-47d3-921b-0b7e67198f7a

📥 Commits

Reviewing files that changed from the base of the PR and between 3f669f9 and da257a4.

⛔ Files ignored due to path filters (1)
  • components/frontend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (48)
  • Makefile
  • components/ambient-sdk/generator/parser.go
  • components/ambient-sdk/go-sdk/client/client.go
  • components/backend/handlers/scheduled_sessions.go
  • components/backend/handlers/scheduled_sessions_test.go
  • components/backend/handlers/sessions_test.go
  • components/backend/handlers/test_helpers_test.go
  • components/backend/main.go
  • components/backend/routes.go
  • components/backend/server/k8s.go
  • components/backend/types/scheduled_session.go
  • components/frontend/package.json
  • components/frontend/src/app/api/projects/[name]/scheduled-sessions/[scheduledSessionName]/resume/route.ts
  • components/frontend/src/app/api/projects/[name]/scheduled-sessions/[scheduledSessionName]/route.ts
  • components/frontend/src/app/api/projects/[name]/scheduled-sessions/[scheduledSessionName]/runs/route.ts
  • components/frontend/src/app/api/projects/[name]/scheduled-sessions/[scheduledSessionName]/suspend/route.ts
  • components/frontend/src/app/api/projects/[name]/scheduled-sessions/[scheduledSessionName]/trigger/route.ts
  • components/frontend/src/app/api/projects/[name]/scheduled-sessions/route.ts
  • components/frontend/src/app/projects/[name]/page.tsx
  • components/frontend/src/app/projects/[name]/scheduled-sessions/[scheduledSessionName]/_components/scheduled-session-details-card.tsx
  • components/frontend/src/app/projects/[name]/scheduled-sessions/[scheduledSessionName]/_components/scheduled-session-runs-table.tsx
  • components/frontend/src/app/projects/[name]/scheduled-sessions/[scheduledSessionName]/page.tsx
  • components/frontend/src/components/chat/AttachmentPreview.tsx
  • components/frontend/src/components/jira-connection-card.tsx
  • components/frontend/src/components/workspace-sections/create-scheduled-session-dialog.tsx
  • components/frontend/src/components/workspace-sections/scheduled-sessions-tab.tsx
  • components/frontend/src/lib/__tests__/cron.test.ts
  • components/frontend/src/lib/cron.ts
  • components/frontend/src/services/api/scheduled-sessions.ts
  • components/frontend/src/services/queries/__tests__/use-scheduled-sessions.test.ts
  • components/frontend/src/services/queries/index.ts
  • components/frontend/src/services/queries/use-scheduled-sessions.ts
  • components/frontend/src/types/api/index.ts
  • components/frontend/src/types/api/scheduled-sessions.ts
  • components/manifests/base/backend-deployment.yaml
  • components/manifests/base/operator-deployment.yaml
  • components/manifests/base/rbac/backend-clusterrole.yaml
  • components/manifests/base/rbac/operator-clusterrole.yaml
  • components/manifests/overlays/e2e/operator-config.yaml
  • components/manifests/overlays/kind/operator-config.yaml
  • components/operator/internal/handlers/projectsettings.go
  • components/operator/internal/handlers/projectsettings_test.go
  • components/operator/internal/trigger/trigger.go
  • components/operator/internal/trigger/trigger_test.go
  • components/operator/main.go
  • components/public-api/handlers/middleware.go
  • components/public-api/main.go
  • scripts/setup-vertex-kind.sh
💤 Files with no reviewable changes (1)
  • components/ambient-sdk/go-sdk/client/client.go

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@components/frontend/src/components/workspace-sections/scheduled-sessions-tab.tsx`:
- Around line 70-79: The handleDelete function currently uses the native
confirm() which provides a generic browser UI; replace it with your app's custom
confirmation dialog (e.g., openConfirmDialog or <ConfirmDialog />) by triggering
the dialog from handleDelete(name) and moving the deleteMutation.mutate call
into the dialog's onConfirm handler so deletion only proceeds after user
confirmation; preserve the same mutation payload ({ projectName, name }) and the
existing onSuccess/onError toast handlers, and ensure the dialog supports
canceling (no-op) and accessible focus management.

In `@components/operator/internal/handlers/projectsettings_test.go`:
- Around line 122-200: Add a negative test that injects API errors via the fake
Kubernetes client to verify ensureSessionTriggerRBAC error handling: copy the
pattern from
TestEnsureSessionTriggerRBAC_Idempotent/TestEnsureSessionTriggerRBAC_MultipleNamespaces
but use setupProjectSettingsTestClient to create a fake client with a reactor
that returns a non-AlreadyExists error (e.g., permission denied) for
ServiceAccount/Role/RoleBinding Create or Get calls, call
ensureSessionTriggerRBAC(namespace, owner) and assert it returns the expected
error, and verify no partial resources were created via
config.K8sClient.CoreV1()/RbacV1() Get calls; reference
ensureSessionTriggerRBAC, setupProjectSettingsTestClient, and config.K8sClient
to locate where to wire the fake reactor and assertions.

In `@components/operator/internal/handlers/projectsettings.go`:
- Around line 153-160: The handler writes a status field
scheduledSessionRBACReady (see statusUpdate in projectsettings.go) that is not
declared in the ProjectSettings CRD schema, which can be dropped or cause
validation errors; update the ProjectSettings CRD schema to add a
status.properties.scheduledSessionRBACReady entry (type: boolean, add a concise
description like "Whether RBAC for scheduled session triggers is ready") and
ensure status is defined as an object so the controller can set that boolean
without schema validation failures.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: a37c9025-22c0-41a9-993f-a9c450a22ff4

📥 Commits

Reviewing files that changed from the base of the PR and between da257a4 and 7b9f763.

📒 Files selected for processing (9)
  • components/backend/handlers/scheduled_sessions.go
  • components/backend/types/scheduled_session.go
  • components/frontend/src/components/workspace-sections/create-scheduled-session-dialog.tsx
  • components/frontend/src/components/workspace-sections/scheduled-sessions-tab.tsx
  • components/frontend/src/services/api/scheduled-sessions.ts
  • components/frontend/src/services/queries/__tests__/use-scheduled-sessions.test.ts
  • components/operator/internal/handlers/projectsettings.go
  • components/operator/internal/handlers/projectsettings_test.go
  • components/operator/internal/trigger/trigger.go

@ambient-code
Copy link
Contributor

ambient-code bot commented Mar 11, 2026

Review Queue — Blockers Found

Check Status Detail
CI pass
Merge conflicts FAIL Has merge conflicts
Review comments FAIL 15 inline threads on components/backend/handlers/scheduled_sessions.go, components/operator/internal/handlers/projectsettings.go
Jira hygiene pass
Fork PR pass ---
Staleness pass

This comment is auto-generated by the Review Queue workflow and will be updated when the PR changes.

mprpic and others added 4 commits March 11, 2026 20:30
Introduce scheduled sessions that let users create cron-based schedules
for automatically launching agentic sessions. The feature spans all
layers of the platform:

Backend: CRUD handlers for scheduled sessions backed by Kubernetes
CronJobs, with suspend/resume, manual trigger, and run-history
endpoints. Auth checks delegate to the user's token while CronJob
operations use the backend service account.

Operator: session-trigger subcommand that runs inside CronJob-spawned
pods, reads a session template from env vars, and creates an
AgenticSession CR. ProjectSettings reconciler ensures per-namespace
RBAC (ServiceAccount, Role, RoleBinding) for the trigger SA.

Frontend: Schedules tab in the project workspace, create dialog with
cron presets and expression preview, detail page with run history,
Next.js API route proxies, React Query hooks, and TypeScript types.

Manifests: Backend ClusterRole gains CronJob and Job/create permissions,
operator ClusterRole gains AgenticSession/create, backend deployment
reads OPERATOR_IMAGE and IMAGE_PULL_POLICY from operator-config
ConfigMap, Vertex AI config keys made optional to avoid clobbering.

Includes unit tests for backend helpers (sanitizeLabelValue,
cronJobToScheduledSession), operator trigger (sanitizeName), operator
RBAC (ensureSessionTriggerRBAC), frontend query hooks, and cron utils.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Martin Prpič <mprpic@redhat.com>
The session creation tests that disable Vertex AI only cleared USE_VERTEX
but not the deprecated CLAUDE_CODE_USE_VERTEX env var. When the latter is
set in the developer's shell, isVertexEnabled() still returns true,
skipping the secret validation and causing a false 201 instead of 400.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Martin Prpič <mprpic@redhat.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Martin Prpič <mprpic@redhat.com>
- Add SelfSubjectAccessReview RBAC checks to all scheduled session
  handlers (list, get, create, update, delete, suspend/resume, trigger)
- Make displayName optional in CreateScheduledSessionRequest
- Fix trigger API response type mismatch (name/namespace vs message)
- Stop leaking Gin binding errors to API responses
- Propagate ensureSessionTriggerRBAC errors into ProjectSettings status
- Add OwnerReferences to SA/Role/RoleBinding created by trigger RBAC
- Add loading and error states to SchedulesSection component
- Fix sanitizeName trailing-hyphen bug (truncate before trim)
- Move create-scheduled-session-dialog to colocated location

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Martin Prpič <mprpic@redhat.com>
@mprpic mprpic force-pushed the scheduled-sessions branch from 7b9f763 to 2548d9c Compare March 12, 2026 00:50
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
components/frontend/src/components/jira-connection-card.tsx (1)

40-49: ⚠️ Potential issue | 🟠 Major

Avoid re-initializing form fields during user edits.

The dependency array includes url and username, causing the effect to rerun whenever the user edits these fields. If the user clears either field while the form is open, the effect immediately restores the default URL or email, blocking the user from clearing or retyping values. Use functional state updates instead and remove the input values from dependencies to only trigger initialization when the form opens or the current user changes.

Suggested fix
   useEffect(() => {
-    if (showForm) {
-      if (!url) {
-        setUrl(DEFAULT_JIRA_URL)
-      }
-      if (!username && currentUser?.email) {
-        setUsername(currentUser.email)
-      }
-    }
-  }, [showForm, currentUser?.email, url, username])
+    if (!showForm) return
+
+    setUrl((prev) => prev || DEFAULT_JIRA_URL)
+    if (currentUser?.email) {
+      setUsername((prev) => prev || currentUser.email)
+    }
+  }, [showForm, currentUser?.email])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/frontend/src/components/jira-connection-card.tsx` around lines 40
- 49, The effect currently re-initializes fields while the user edits because
url and username are in the dependency array; change the effect to only depend
on showForm and currentUser?.email and use functional state updates so you only
initialize when fields are empty. In the useEffect (the one referencing
showForm, url, username), remove url and username from the deps (use [showForm,
currentUser?.email]) and replace direct
setUrl(DEFAULT_JIRA_URL)/setUsername(currentUser.email) with functional updates
like setUrl(prev => prev ?? DEFAULT_JIRA_URL) and setUsername(prev => prev ??
currentUser?.email) so existing user edits are not overwritten.
♻️ Duplicate comments (2)
components/frontend/src/services/api/scheduled-sessions.ts (1)

14-95: ⚠️ Potential issue | 🟡 Minor

URL-encode path parameters to prevent malformed requests.

All functions in this file interpolate projectName and name directly into URL paths. If these contain special characters (e.g., /, ?, #, spaces), the URLs will be malformed. Apply encodeURIComponent() to all path parameters.

Example fix pattern
 export async function listScheduledSessions(
   projectName: string
 ): Promise<ScheduledSession[]> {
   const response = await apiClient.get<{ items: ScheduledSession[] }>(
-    `/projects/${projectName}/scheduled-sessions`
+    `/projects/${encodeURIComponent(projectName)}/scheduled-sessions`
   );
   return response.items;
 }

Apply the same pattern to projectName and name in all 9 functions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/frontend/src/services/api/scheduled-sessions.ts` around lines 14 -
95, The path parameters (projectName and name) are interpolated raw into URLs
causing malformed requests for special characters; update every function that
builds a URL—listScheduledSessions, createScheduledSession, getScheduledSession,
updateScheduledSession, deleteScheduledSession, suspendScheduledSession,
resumeScheduledSession, triggerScheduledSession, and listScheduledSessionRuns—to
wrap projectName and name with encodeURIComponent() before constructing the path
so all generated URLs are properly escaped.
components/backend/handlers/scheduled_sessions.go (1)

239-301: 🧹 Nitpick | 🔵 Trivial

Read-modify-write without conflict retry (existing pattern).

The Get-then-Update pattern can fail with a conflict error if another client modifies the CronJob concurrently. Kubernetes protects against silent data loss via resourceVersion, so the current behavior is safe but may return 500 errors to users on rare conflicts. Consider adding retry logic for errors.IsConflict(err) to improve UX.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/backend/handlers/scheduled_sessions.go` around lines 239 - 301,
The update handler uses a read-modify-write on the CronJob (cj) via
K8sClientScheduled.BatchV1().CronJobs(...).Get and .Update and should retry on
Kubernetes conflict errors; modify the update logic (around
K8sClientScheduled.BatchV1().CronJobs(...).Update and the preceding cj
modifications) to perform a small retry loop (e.g., 3 attempts with brief
backoff) that on errors.IsConflict(err) re-fetches the latest cj (using the same
Get call), reapplies the requested changes from req (Schedule, Suspend,
DisplayName, SessionTemplate) to the fresh cj, and retries Update, returning the
original error if non-conflict or retries exhausted; ensure you preserve the
annotation and SESSION_TEMPLATE env var logic when reapplying changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@components/backend/handlers/scheduled_sessions.go`:
- Around line 120-121: The generated schedule name using timestamp (variables
timestamp and name) can collide for concurrent creates; change the name
construction (the fmt.Sprintf("schedule-%d", timestamp) call) to append a short
random suffix (e.g., 4–6 hex or base36 chars) to make it unique—use a secure
random source (crypto/rand) or UnixNano combined with a small math/rand value
(and seed if using math/rand), convert the bytes/int to a short string, and
include it in the fmt.Sprintf (e.g., "schedule-%d-%s"); if using crypto/rand,
handle any error from the reader. Ensure imports are added/updated accordingly.
- Around line 486-489: The code silently ignores JSON unmarshal errors for
SESSION_TEMPLATE; update the block that unmarshals into
types.CreateAgenticSessionRequest so that when json.Unmarshal returns an error
you log the error (including the erroneous env.Value and the error) using the
repository's existing logger (or fmt.Errorf/log.Printf if none), and only assign
to ss.SessionTemplate when unmarshalling succeeds; ensure the log message
includes context like "failed to parse SESSION_TEMPLATE" and the error details
to aid debugging.

In `@components/backend/handlers/sessions_test.go`:
- Around line 443-448: The test saves env vars using os.Getenv which cannot
distinguish unset vs empty; change the capture to use os.LookupEnv for
USE_VERTEX and CLAUDE_CODE_USE_VERTEX (e.g., originalVertexValuePresent,
originalVertexValue, originalClaudeVertexValuePresent,
originalClaudeVertexValue) and update the deferred restore logic in the test
(around the originalVertexValue/originalClaudeVertexValue setup and defers in
sessions_test.go) to call os.Unsetenv when the var was originally absent and
os.Setenv when it was present, so the tests restore the original
presence/absence rather than always setting the variables back.

In `@components/backend/server/k8s.go`:
- Around line 85-95: The code currently falls back to a floating "latest"
operator image which can cause mismatches; change the logic around OperatorImage
and ImagePullPolicy so OPERATOR_IMAGE must be explicitly provided or the service
fails fast: if os.Getenv("OPERATOR_IMAGE") returns empty, return an error (or
log fatal) instead of defaulting to
"quay.io/ambient_code/vteam_operator:latest"; likewise, remove the implicit
ImagePullPolicy default of "IfNotPresent" when using an unspecified tag — either
require IMAGE_PULL_POLICY to be set or set ImagePullPolicy="Always" when a
non-pinned tag is used; update references to OperatorImage and ImagePullPolicy
in k8s setup code to reflect this validation and fail-fast behavior.

In
`@components/frontend/src/app/api/projects/`[name]/scheduled-sessions/[scheduledSessionName]/trigger/route.ts:
- Around line 15-16: The handler currently always calls response.text() and
returns new Response(text, ...) which will throw for null-body statuses (204,
205, 304); update the logic in the route handler to first check response.status
and if it is one of 204/205/304 return new Response(null, { status:
response.status }) (omit Content-Type), otherwise await response.text() and
return new Response(text, { status: response.status, headers: { 'Content-Type':
'application/json' } }); reference the existing variables/expressions response,
response.status, response.text(), text and new Response when locating the fix.

In
`@components/frontend/src/app/projects/`[name]/scheduled-sessions/[scheduledSessionName]/page.tsx:
- Around line 44-48: The page currently treats a missing scheduledSession the
same for all failures because it only checks scheduledSession/undefined; update
the render logic around useScheduledSession and useScheduledSessionRuns to
distinguish network/auth/server errors from a true 404: use the hook's isError
and error (from useScheduledSession) to render an explicit error state for
non-404 errors (e.g., 403/500/network), and only show the "not found" 404-style
UI when the error indicates a 404 status or when scheduledSession is explicitly
null/missing and there is no other error; apply the same distinction for the
runs block (lines 108-114) so useScheduledSessionRuns' isError/error are handled
separately from a genuine not-found case.

In
`@components/frontend/src/components/workspace-sections/create-scheduled-session-dialog.tsx`:
- Around line 116-120: The useEffect currently depends on the whole form object;
change its dependency array to only the specific stable values used:
modelsData?.defaultModel, form.formState.dirtyFields.model, and form.setValue.
Keep the effect body the same (checking modelsData?.defaultModel and
!form.formState.dirtyFields.model, then calling form.setValue("model", ... , {
shouldDirty: false })), but replace the dependency list in the useEffect
declaration so it reads something like useEffect(..., [modelsData?.defaultModel,
form.formState.dirtyFields.model, form.setValue]) to make triggers explicit and
avoid depending on the entire form reference.

In
`@components/frontend/src/components/workspace-sections/scheduled-sessions-tab.tsx`:
- Around line 148-159: The Link href in scheduled-sessions-tab.tsx uses
projectName directly which can break URLs with special characters; update the
href to use encodeURIComponent(projectName) so it matches the runs table
behavior. Locate the Link element that builds the path
`/projects/${projectName}/scheduled-sessions/${ss.name}` (inside the
scheduled-sessions Tab component) and wrap projectName with encodeURIComponent
before interpolation; keep ss.name as-is if already encoded elsewhere or apply
encodeURIComponent there as well for consistency. Ensure the className and inner
JSX remain unchanged and run the app to verify links navigate correctly.

In `@components/manifests/base/rbac/backend-clusterrole.yaml`:
- Around line 67-70: The ClusterRole named backend-api currently grants
cluster-wide management of CronJobs (apiGroups: ["batch"], resources:
["cronjobs"], verbs: ["get","list","watch","create","update","patch","delete"])
which triggers Trivy KSV-0048; either restrict this by replacing the ClusterRole
permission with a namespace-scoped Role bound to the backend service account per
project namespace, or explicitly document/approve the tradeoff. Update the
manifest by removing or narrowing the cronjobs rules from the ClusterRole and
instead create a Role (or Roles) with the same verbs scoped to target namespaces
and bind them via RoleBinding(s) to the backend service account, or add a clear
comment and security-approval note on the ClusterRole if cluster-wide management
is intentional.

In `@components/operator/internal/handlers/projectsettings.go`:
- Around line 257-316: ensureSessionTriggerRBAC currently treats AlreadyExists
as success which can hide drift; update it so when Create returns AlreadyExists
you fetch the existing object (use K8sClient.CoreV1().ServiceAccounts(...).Get,
RbacV1().Roles(...).Get, RbacV1().RoleBindings(...).Get), compare and reconcile
the live object's important fields (for ServiceAccount: labels and
OwnerReferences; for Role: Rules, labels and OwnerReferences; for RoleBinding:
RoleRef, Subjects, labels and OwnerReferences) and perform an Update or Patch to
align the live resource with the desired sa/role/rb (saName, roleName, rbName)
using optimistic retries to handle conflicts; ensure you avoid changing
immutable fields (e.g., namespace/name) and preserve other existing fields not
managed by us.

In `@components/public-api/main.go`:
- Around line 145-149: The CRC origin pattern "https://*.apps-crc.testing" in
the returned origins slice must be matched by enabling wildcard support in the
CORS middleware: set AllowWildcard (or the equivalent option on the CORS config
used with this origins slice) to true where the CORS options are constructed so
that the returned []string including "https://*.apps-crc.testing" is honored;
update the CORS configuration that consumes this origins list (the
middleware/option construction) to include AllowWildcard: true.

---

Outside diff comments:
In `@components/frontend/src/components/jira-connection-card.tsx`:
- Around line 40-49: The effect currently re-initializes fields while the user
edits because url and username are in the dependency array; change the effect to
only depend on showForm and currentUser?.email and use functional state updates
so you only initialize when fields are empty. In the useEffect (the one
referencing showForm, url, username), remove url and username from the deps (use
[showForm, currentUser?.email]) and replace direct
setUrl(DEFAULT_JIRA_URL)/setUsername(currentUser.email) with functional updates
like setUrl(prev => prev ?? DEFAULT_JIRA_URL) and setUsername(prev => prev ??
currentUser?.email) so existing user edits are not overwritten.

---

Duplicate comments:
In `@components/backend/handlers/scheduled_sessions.go`:
- Around line 239-301: The update handler uses a read-modify-write on the
CronJob (cj) via K8sClientScheduled.BatchV1().CronJobs(...).Get and .Update and
should retry on Kubernetes conflict errors; modify the update logic (around
K8sClientScheduled.BatchV1().CronJobs(...).Update and the preceding cj
modifications) to perform a small retry loop (e.g., 3 attempts with brief
backoff) that on errors.IsConflict(err) re-fetches the latest cj (using the same
Get call), reapplies the requested changes from req (Schedule, Suspend,
DisplayName, SessionTemplate) to the fresh cj, and retries Update, returning the
original error if non-conflict or retries exhausted; ensure you preserve the
annotation and SESSION_TEMPLATE env var logic when reapplying changes.

In `@components/frontend/src/services/api/scheduled-sessions.ts`:
- Around line 14-95: The path parameters (projectName and name) are interpolated
raw into URLs causing malformed requests for special characters; update every
function that builds a URL—listScheduledSessions, createScheduledSession,
getScheduledSession, updateScheduledSession, deleteScheduledSession,
suspendScheduledSession, resumeScheduledSession, triggerScheduledSession, and
listScheduledSessionRuns—to wrap projectName and name with encodeURIComponent()
before constructing the path so all generated URLs are properly escaped.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0dfd1b92-c87f-4679-aac8-e17a6796c80b

📥 Commits

Reviewing files that changed from the base of the PR and between 7b9f763 and 2548d9c.

⛔ Files ignored due to path filters (1)
  • components/frontend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (47)
  • Makefile
  • components/backend/handlers/scheduled_sessions.go
  • components/backend/handlers/scheduled_sessions_test.go
  • components/backend/handlers/sessions_test.go
  • components/backend/handlers/test_helpers_test.go
  • components/backend/main.go
  • components/backend/routes.go
  • components/backend/server/k8s.go
  • components/backend/types/scheduled_session.go
  • components/frontend/package.json
  • components/frontend/src/app/api/projects/[name]/scheduled-sessions/[scheduledSessionName]/resume/route.ts
  • components/frontend/src/app/api/projects/[name]/scheduled-sessions/[scheduledSessionName]/route.ts
  • components/frontend/src/app/api/projects/[name]/scheduled-sessions/[scheduledSessionName]/runs/route.ts
  • components/frontend/src/app/api/projects/[name]/scheduled-sessions/[scheduledSessionName]/suspend/route.ts
  • components/frontend/src/app/api/projects/[name]/scheduled-sessions/[scheduledSessionName]/trigger/route.ts
  • components/frontend/src/app/api/projects/[name]/scheduled-sessions/route.ts
  • components/frontend/src/app/projects/[name]/page.tsx
  • components/frontend/src/app/projects/[name]/scheduled-sessions/[scheduledSessionName]/_components/scheduled-session-details-card.tsx
  • components/frontend/src/app/projects/[name]/scheduled-sessions/[scheduledSessionName]/_components/scheduled-session-runs-table.tsx
  • components/frontend/src/app/projects/[name]/scheduled-sessions/[scheduledSessionName]/page.tsx
  • components/frontend/src/components/chat/AttachmentPreview.tsx
  • components/frontend/src/components/jira-connection-card.tsx
  • components/frontend/src/components/workspace-sections/create-scheduled-session-dialog.tsx
  • components/frontend/src/components/workspace-sections/scheduled-sessions-tab.tsx
  • components/frontend/src/lib/__tests__/cron.test.ts
  • components/frontend/src/lib/cron.ts
  • components/frontend/src/services/api/scheduled-sessions.ts
  • components/frontend/src/services/queries/__tests__/use-scheduled-sessions.test.ts
  • components/frontend/src/services/queries/index.ts
  • components/frontend/src/services/queries/use-scheduled-sessions.ts
  • components/frontend/src/types/api/index.ts
  • components/frontend/src/types/api/scheduled-sessions.ts
  • components/manifests/base/backend-deployment.yaml
  • components/manifests/base/crds/projectsettings-crd.yaml
  • components/manifests/base/operator-deployment.yaml
  • components/manifests/base/rbac/backend-clusterrole.yaml
  • components/manifests/base/rbac/operator-clusterrole.yaml
  • components/manifests/overlays/e2e/operator-config.yaml
  • components/manifests/overlays/kind/operator-config.yaml
  • components/operator/internal/handlers/projectsettings.go
  • components/operator/internal/handlers/projectsettings_test.go
  • components/operator/internal/trigger/trigger.go
  • components/operator/internal/trigger/trigger_test.go
  • components/operator/main.go
  • components/public-api/handlers/middleware.go
  • components/public-api/main.go
  • scripts/setup-vertex-kind.sh

@mergify mergify bot merged commit 8420727 into main Mar 12, 2026
35 checks passed
@mergify mergify bot deleted the scheduled-sessions branch March 12, 2026 12:38
@mergify
Copy link

mergify bot commented Mar 12, 2026

Merge Queue Status

  • Entered queue2026-03-12 12:38 UTC · Rule: default
  • Checks passed · in-place
  • Merged2026-03-12 12:38 UTC · at 2548d9cef1669d8301a4d67c88a653edb1cc9fb0

This pull request spent 5 seconds in the queue, with no time running CI.

Required conditions to merge

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