Skip to content

fix(operator): apply feature flags when scheduled sessions trigger#1572

Open
janisz wants to merge 14 commits into
ambient-code:mainfrom
janisz:fix/scheduled-session-feature-flags
Open

fix(operator): apply feature flags when scheduled sessions trigger#1572
janisz wants to merge 14 commits into
ambient-code:mainfrom
janisz:fix/scheduled-session-feature-flags

Conversation

@janisz
Copy link
Copy Markdown

@janisz janisz commented May 13, 2026

Fixes #1506 - Scheduled sessions were not respecting workspace feature flags like jira-write.

Root Cause

The session-trigger command creates AgenticSession CRs directly from a pre-stored template without re-evaluating workspace feature flags at trigger time. When regular sessions are created via the backend API, they check the jira-write flag and set JIRA_READ_ONLY_MODE=false if enabled. Scheduled sessions skipped this check entirely.

Solution

Added applyFeatureFlagOverrides() function that:

  1. Reads the feature-flag-overrides ConfigMap from the workspace namespace
  2. Checks if jira-write is set to "true"
  3. Sets JIRA_READ_ONLY_MODE=false in the session template's environment variables before creating the CR

The implementation follows the same pattern as CreateAgenticSession in the backend: read ConfigMap, check flag value, inject environment variable. Errors are logged but non-fatal to ensure degraded operation.

Summary by CodeRabbit

  • New Features

    • Workspace-level feature-flag overrides now control Jira integration behavior (can disable read-only mode per workspace).
  • Bug Fixes

    • Improved robustness when reading workspace overrides: missing or unreadable override data no longer blocks session creation.
  • Tests

    • Added comprehensive tests validating override handling and preservation of existing environment settings.

Scheduled sessions were not respecting workspace feature flags like
`jira-write` because the session-trigger command creates AgenticSession
CRs directly from a pre-stored template without re-evaluating feature
flags at trigger time.

This fix adds applyFeatureFlagOverrides() to read the workspace's
feature-flag-overrides ConfigMap and apply settings (e.g., setting
JIRA_READ_ONLY_MODE=false when jira-write is enabled) before creating
the session CR.

The implementation follows the same pattern as CreateAgenticSession in
the backend: read ConfigMap, check flag value, inject environment
variable. Errors are logged but non-fatal to ensure degraded operation.

Fixes ambient-code#1506

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@netlify
Copy link
Copy Markdown

netlify Bot commented May 13, 2026

Deploy Preview for cheerful-kitten-f556a0 canceled.

Name Link
🔨 Latest commit 513ff9d
🔍 Latest deploy log https://app.netlify.com/projects/cheerful-kitten-f556a0/deploys/6a05f66d3df5690008cea5c6

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 13, 2026

📝 Walkthrough

Walkthrough

Adds workspace-level feature-flag overrides: operator reads a feature-flag-overrides ConfigMap and, when jira-write: "true", injects JIRA_READ_ONLY_MODE="false" into the session template before creating the AgenticSession. ConfigMap read failures are logged and do not block session creation.

Changes

Workspace Feature-Flag Overrides for JIRA Write Access

Layer / File(s) Summary
Kubernetes client initialization and reuse
components/operator/internal/trigger/trigger.go
RunSessionTrigger now initializes Kubernetes clients via config.InitK8sClients() and uses config.DynamicClient for reuse/creation instead of constructing an in-file dynamic client with rest.InClusterConfig().
Feature-flag override helper
components/operator/internal/trigger/trigger.go
Added applyFeatureFlagOverrides which reads the feature-flag-overrides ConfigMap in the target namespace (typed client usage); if data["jira-write"] == "true" it ensures spec.environmentVariables["JIRA_READ_ONLY_MODE"] = "false" in the session template. Missing/errored lookups are treated as non-fatal (logged warnings).
Session creation integration
components/operator/internal/trigger/trigger.go
createNewSession calls applyFeatureFlagOverrides when config.K8sClient != nil with a ~10s timeout before creating the AgenticSession. The dynamic-client Create call keeps a separate 30s timeout context for CR creation.
Constants for config and env var keys
components/operator/internal/types/resources.go
New exported constants: FeatureFlagOverridesConfigMap = "feature-flag-overrides", JiraWriteFlagKey = "jira-write", JiraReadOnlyModeEnvVar = "JIRA_READ_ONLY_MODE".
Unit tests for override behavior
components/operator/internal/trigger/trigger_test.go
Added TestApplyFeatureFlagOverrides (table-driven) using a fake Kubernetes clientset and ConfigMap permutations to assert that JIRA_READ_ONLY_MODE is added/overridden only when jira-write: "true", and that pre-existing environment variables are preserved.

Sequence Diagram

sequenceDiagram
  participant Operator as Operator (trigger)
  participant ConfigClient as K8s API (ConfigMap)
  participant Dynamic as DynamicClient (AgenticSession CR)
  participant Template as Session Template

  Operator->>ConfigClient: GET ConfigMap "feature-flag-overrides" (namespace)
  alt ConfigMap exists and jira-write == "true"
    ConfigClient-->>Operator: ConfigMap data
    Operator->>Template: set spec.environmentVariables["JIRA_READ_ONLY_MODE"]="false"
  else ConfigMap missing / jira-write != "true"
    ConfigClient-->>Operator: NotFound or non-true value
    Operator-->>Template: leave env vars unchanged
  end
  Operator->>Dynamic: CREATE AgenticSession CR (30s ctx) with Template
  Dynamic-->>Operator: creation result
Loading

Possibly Related PRs

  • ambient-code/platform#1138: Implements the same jira-write workspace feature-flag behavior via the feature-flag-overrides ConfigMap and JIRA_READ_ONLY_MODE injection.
  • ambient-code/platform#1136: Modifies the same createNewSession / reuse flow in components/operator/internal/trigger/trigger.go.
🚥 Pre-merge checks | ✅ 7 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (7 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title follows Conventional Commits format (fix(scope): description) and accurately describes the main change: applying feature flags when sessions trigger.
Linked Issues check ✅ Passed PR fully addresses #1506 by implementing applyFeatureFlagOverrides() to read feature flags at session trigger time and inject JIRA_READ_ONLY_MODE when jira-write is enabled.
Out of Scope Changes check ✅ Passed All changes are directly scoped to #1506: refactored K8s client initialization, added feature-flag evaluation logic, introduced constants for flag names, and added comprehensive tests.
Performance And Algorithmic Complexity ✅ Passed No performance regressions. applyFeatureFlagOverrides adds single ConfigMap.Get per session (10s timeout). No O(n²), N+1 patterns, or expensive loop ops. K8s clients initialized once and reused.
Security And Secret Handling ✅ Passed No security violations. Token uses standard k8s mounts; ConfigMap reads use hardcoded constants; env vars set with literals; RBAC enforced by k8s API; logs sanitized.
Kubernetes Resource Safety ✅ Passed No K8s resource safety issues. Operations are namespace-scoped, RBAC read-only (get verb), resource limits/requests present, no child resources missing OwnerReferences.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
✨ Simplify code
  • Create PR with simplified code

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 and usage tips.

Copy link
Copy Markdown
Contributor

@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: 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 `@components/operator/internal/trigger/trigger_test.go`:
- Around line 179-185: The test fixture nests the session spec under "spec"
which doesn't match the real runtime input consumed by
applyFeatureFlagOverrides; update the test to pass the spec map directly (not
wrapped as {"spec": ...}) and adjust assertions to inspect top-level keys (e.g.,
check environmentVariables on the map passed to applyFeatureFlagOverrides),
replacing usages of template["spec"] and any other occurrences in this file
(also update the similar block around the other failing assertions) so the test
exercises the same contract as applyFeatureFlagOverrides.

In `@components/operator/internal/trigger/trigger.go`:
- Around line 268-282: The code is writing environmentVariables into
template["spec"] producing spec.spec.environmentVariables; treat template as the
AgenticSession spec instead and set environmentVariables directly on template.
Update the logic that creates/reads spec and envVars (the variables named
template, spec and envVars) so you either assign spec =
template.(map[string]interface{}) or read envVars from
template["environmentVariables"] (not template["spec"]["environmentVariables"]),
then set envVars["JIRA_READ_ONLY_MODE"]="false" and store it back to
template["environmentVariables"] (or spec["environmentVariables"] if you
switched spec to reference template).
🪄 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: Enterprise

Run ID: 058bc2ee-3c97-41f4-9304-fb8add42c69b

📥 Commits

Reviewing files that changed from the base of the PR and between ecdcd89 and adf081b.

📒 Files selected for processing (2)
  • components/operator/internal/trigger/trigger.go
  • components/operator/internal/trigger/trigger_test.go

Comment on lines +179 to +185
template := map[string]interface{}{
"spec": map[string]interface{}{},
}
if tt.existingEnvVars != nil {
spec := template["spec"].(map[string]interface{})
spec["environmentVariables"] = tt.existingEnvVars
}
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Test fixture uses a nested spec shape that does not match runtime input.

applyFeatureFlagOverrides is called with the session spec map in production, but this test passes {"spec": {...}} and asserts inside template["spec"]. That can mask real regressions and currently validates the wrong contract.

Suggested fix
-			template := map[string]interface{}{
-				"spec": map[string]interface{}{},
-			}
+			template := map[string]interface{}{}
 			if tt.existingEnvVars != nil {
-				spec := template["spec"].(map[string]interface{})
-				spec["environmentVariables"] = tt.existingEnvVars
+				template["environmentVariables"] = tt.existingEnvVars
 			}
@@
-			spec, ok := template["spec"].(map[string]interface{})
-			if !ok {
-				t.Fatal("template[spec] is not a map")
-			}
-
-			envVars, ok := spec["environmentVariables"].(map[string]interface{})
+			envVars, ok := template["environmentVariables"].(map[string]interface{})
 			if tt.expectedEnvVars == nil {

Also applies to: 195-210

🤖 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 `@components/operator/internal/trigger/trigger_test.go` around lines 179 - 185,
The test fixture nests the session spec under "spec" which doesn't match the
real runtime input consumed by applyFeatureFlagOverrides; update the test to
pass the spec map directly (not wrapped as {"spec": ...}) and adjust assertions
to inspect top-level keys (e.g., check environmentVariables on the map passed to
applyFeatureFlagOverrides), replacing usages of template["spec"] and any other
occurrences in this file (also update the similar block around the other failing
assertions) so the test exercises the same contract as
applyFeatureFlagOverrides.

Comment on lines +268 to +282
spec, ok := template["spec"].(map[string]interface{})
if !ok {
spec = map[string]interface{}{}
template["spec"] = spec
}

envVars, ok := spec["environmentVariables"].(map[string]interface{})
if !ok {
envVars = map[string]interface{}{}
}

// Set JIRA_READ_ONLY_MODE to false to enable write operations
envVars["JIRA_READ_ONLY_MODE"] = "false"
spec["environmentVariables"] = envVars

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Feature-flag override is written to the wrong object level (spec.spec).

template is already the AgenticSession spec (Line 337 sets "spec": template), but this function writes to template["spec"]["environmentVariables"]. That produces spec.spec.environmentVariables, so JIRA_READ_ONLY_MODE won’t be applied where runtime expects it.

Suggested fix
-		// Get or create environmentVariables map
-		spec, ok := template["spec"].(map[string]interface{})
-		if !ok {
-			spec = map[string]interface{}{}
-			template["spec"] = spec
-		}
-
-		envVars, ok := spec["environmentVariables"].(map[string]interface{})
+		// template is already the session spec; write env vars at this level.
+		envVars, ok := template["environmentVariables"].(map[string]interface{})
 		if !ok {
 			envVars = map[string]interface{}{}
 		}
 
 		// Set JIRA_READ_ONLY_MODE to false to enable write operations
 		envVars["JIRA_READ_ONLY_MODE"] = "false"
-		spec["environmentVariables"] = envVars
+		template["environmentVariables"] = envVars

Also applies to: 337-337

🤖 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 `@components/operator/internal/trigger/trigger.go` around lines 268 - 282, The
code is writing environmentVariables into template["spec"] producing
spec.spec.environmentVariables; treat template as the AgenticSession spec
instead and set environmentVariables directly on template. Update the logic that
creates/reads spec and envVars (the variables named template, spec and envVars)
so you either assign spec = template.(map[string]interface{}) or read envVars
from template["environmentVariables"] (not
template["spec"]["environmentVariables"]), then set
envVars["JIRA_READ_ONLY_MODE"]="false" and store it back to
template["environmentVariables"] (or spec["environmentVariables"] if you
switched spec to reference template).

janisz and others added 2 commits May 13, 2026 17:33
- Add constants for feature flag strings (FeatureFlagOverridesConfigMap,
  JiraWriteFlagKey, JiraReadOnlyModeEnvVar) to replace hardcoded values
- Reuse config.K8sClient and config.DynamicClient initialized in
  RunSessionTrigger instead of creating clients on every session
- Flatten nested conditionals in createNewSession using early returns
- Remove WHAT comments that duplicate code, keeping only WHY comments

This addresses code review findings: stringly-typed code, redundant
client creation, and unnecessary comments.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

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

♻️ Duplicate comments (1)
components/operator/internal/trigger/trigger.go (1)

256-269: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Write the override into the session spec, not spec.spec.

Line 256 treats template as if it contains a nested spec, but Line 304 already stores template as the CR spec. This produces spec.spec.environmentVariables, so JIRA_READ_ONLY_MODE won’t be applied on the expected path.

Suggested fix
-	spec, ok := template["spec"].(map[string]interface{})
-	if !ok {
-		spec = map[string]interface{}{}
-		template["spec"] = spec
-	}
-
-	envVars, ok := spec["environmentVariables"].(map[string]interface{})
+	envVars, ok := template["environmentVariables"].(map[string]interface{})
 	if !ok {
 		envVars = map[string]interface{}{}
 	}
 
 	envVars[types.JiraReadOnlyModeEnvVar] = "false"
-	spec["environmentVariables"] = envVars
+	template["environmentVariables"] = envVars
🤖 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 `@components/operator/internal/trigger/trigger.go` around lines 256 - 269, The
code is writing the env override into a nested spec map (creating
spec["environmentVariables"] under template["spec"]), which yields
spec.spec.environmentVariables; instead, locate the session spec map that was
stored as the CR spec (the top-level template map) and set its environment
variable there—so remove the intermediate spec nesting and write
types.JiraReadOnlyModeEnvVar = "false" directly into
template["environmentVariables"] (or into the sessionSpec map variable if one
exists), ensuring you update the same map that is later used as the CR spec (the
template variable), not a nested spec field.
🤖 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.

Duplicate comments:
In `@components/operator/internal/trigger/trigger.go`:
- Around line 256-269: The code is writing the env override into a nested spec
map (creating spec["environmentVariables"] under template["spec"]), which yields
spec.spec.environmentVariables; instead, locate the session spec map that was
stored as the CR spec (the top-level template map) and set its environment
variable there—so remove the intermediate spec nesting and write
types.JiraReadOnlyModeEnvVar = "false" directly into
template["environmentVariables"] (or into the sessionSpec map variable if one
exists), ensuring you update the same map that is later used as the CR spec (the
template variable), not a nested spec field.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 27f959f8-b42b-4b92-a943-4db44d18de89

📥 Commits

Reviewing files that changed from the base of the PR and between adf081b and 3526563.

📒 Files selected for processing (3)
  • components/operator/internal/trigger/trigger.go
  • components/operator/internal/trigger/trigger_test.go
  • components/operator/internal/types/resources.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • components/operator/internal/trigger/trigger_test.go

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.

Bug: Intermittent failure to register jira-write MCP tools in active sessions

1 participant