Skip to content

feat(cloudbees): Operations Center + Feature Management enterprise support#468

Open
beng360 wants to merge 19 commits into
mainfrom
feat/cloudbees-enterprise
Open

feat(cloudbees): Operations Center + Feature Management enterprise support#468
beng360 wants to merge 19 commits into
mainfrom
feat/cloudbees-enterprise

Conversation

@beng360
Copy link
Copy Markdown
Contributor

@beng360 beng360 commented Jun 2, 2026

Summary

Enhances the CloudBees connector from a simple Jenkins reskin to full enterprise support with Operations Center multi-controller management and Feature Management flag correlation.

What's new

Operations Center Client

  • Discovers all managed Jenkins controllers from a central OC instance
  • Queries builds across multiple controllers in parallel (capped at 20 controllers, 5 builds each)
  • SSRF protection: validates controller URLs match OC domain before sending credentials

Feature Management Client

  • Connects to CloudBees Feature Management API (Bearer token auth)
  • Queries recent flag changes for incident correlation ("was a flag toggled before this broke?")
  • 1 req/sec rate limiting with single retry on 429
  • URL-encoded path parameters (prevents path injection)

Enhanced RCA Tool — 3 new actions

  • flag_changes — "show me recent feature flag toggles that might correlate with this incident"
  • cross_controller_deployments — "what deployed across all controllers in the last 4 hours?"
  • controller_list — "what controllers are managed by this Operations Center?"

New routes

  • POST /connect-platform — connect OC + FM credentials
  • GET /platform-status — check what's connected
  • GET /controllers — list discovered controllers
  • POST /disconnect-platform — remove platform credentials

Architecture

Existing (unchanged):
  /connect → single Jenkins controller → cloudbees_rca actions

New (additive):
  /connect-platform → OC credentials (cloudbees_oc provider)
                     → FM credentials (cloudbees_fm provider)
  
  RCA agent → cloudbees_rca(action="flag_changes")
            → cloudbees_rca(action="cross_controller_deployments")  
            → cloudbees_rca(action="controller_list")

Security hardening (from review)

  • Path injection: app_id/flag_name URL-encoded via urllib.parse.quote
  • SSRF: controller URLs validated against OC domain before credential forwarding
  • Resource leaks: HTTP clients support context managers + explicit close()
  • Error sanitization: no raw exceptions returned to callers
  • URL scheme validation: only http/https accepted
  • Rate limit: FM client respects 1 req/sec + retries on 429

Backwards compatibility

  • Existing single-controller flow (/connect, all existing RCA actions) completely unchanged
  • New actions gracefully return "not_configured" message when OC/FM credentials don't exist
  • No database schema changes (uses existing user_tokens table with new provider names)

Test plan

  • Connect single CloudBees controller (existing flow still works)
  • Connect Operations Center — verify controller discovery
  • Connect Feature Management — verify token validation
  • Trigger RCA with flag_changes action — verify flag data returned
  • Trigger RCA with cross_controller_deployments — verify cross-controller aggregation
  • Test without OC/FM credentials — verify graceful "not_configured" messages
  • Test with invalid OC URL — verify scheme validation rejects non-http

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added CloudBees Platform (Enterprise) connector supporting Operations Center and Feature Management integration
    • Introduced enterprise RCA investigation actions for cross-controller deployments, controller discovery, and feature flag changes
    • Added platform status and controller discovery API endpoints
    • Implemented enhanced connection UI with multi-step authentication workflow
  • Documentation

    • Added comprehensive CloudBees integration documentation including setup and webhook configuration
    • Updated skill documentation for new enterprise actions
  • Configuration

    • Added feature flag to enable/disable CloudBees connector

@beng360 beng360 requested a review from a team as a code owner June 2, 2026 21:06
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 2, 2026

Review Change Stack

Walkthrough

This PR extends the CloudBees CI integration with an enterprise platform surface supporting Operations Center and Feature Management APIs. It introduces new connector clients for both services, server credential routes for managing platform credentials, extended RCA actions for cross-controller deployments and feature-flag queries, a multi-step auth UI for the three connection modes, and updated documentation with enterprise action guidance.

Changes

CloudBees Enterprise Platform Integration

Layer / File(s) Summary
Feature Management (FM) Client
server/connectors/cloudbees_connector/fm_client.py
CloudBeesFMClient manages bearer-authenticated, rate-limited HTTP requests to the Rollout API, validates tokens, lists applications, filters recent flag changes by modification timestamp, and retrieves individual flag status.
Operations Center (OC) Client
server/connectors/cloudbees_connector/oc_client.py
CloudBeesOCClient normalizes URLs, selects auth mode (basic/bearer), discovers controllers via layered endpoint fallbacks, validates controller domain ownership, and queries recent builds across controllers with per-controller job and per-job build caps.
Platform Orchestration Client
server/connectors/cloudbees_connector/platform_client.py
CloudBeesPlatformClient composes OC and FM clients to return structured deployment and flag-change contexts independently, avoiding cross-client failures.
Enterprise Platform Credential Routes
server/routes/cloudbees/cloudbees_routes.py
Four new endpoints manage platform credentials: /connect-platform validates and persists OC/FM credentials plus discovers controllers; /platform-status checks credential presence; /controllers runs discovery; /disconnect-platform deletes both. Also stricter /connect URL scheme validation and improved credential-failure logging.
RCA Tool Enterprise Actions
server/chat/backend/agent/tools/cloudbees_rca_tool.py, server/chat/backend/agent/tools/cloud_tools.py, server/chat/background/prediscovery_task.py
Extended CloudBeesRCAArgs with app_id field and three new actions: flag_changes (FM, requires app_id), cross_controller_deployments (OC), controller_list (OC). Added per-user OC/FM client builders, removed trace_context delegation, updated tool descriptions and prediscovery instructions.
Client API Route Handlers
client/src/app/api/cloudbees/connect-platform/route.ts, client/src/app/api/cloudbees/controllers/route.ts, client/src/app/api/cloudbees/platform-status/route.ts
Three new Next.js API routes configured via createCIPostHandler/createCIGetHandler factories for platform endpoint delegation.
Multi-Step CloudBees Auth Flow
client/src/app/cloudbees/auth/components/CloudBeesAuthPage.tsx
New React component implementing three-step UX: step 1 mode selection (Single Controller, Operations Center, PAT), step 2 credential forms with URL scheme validation, step 3 webhook snippet display; connected view shows metadata, summary stats, RCA toggle, webhook UI, recent deployments, and managed controllers.
Auth Wrapper and Feature Flag Gating
client/src/app/cloudbees/auth/page.tsx, client/src/lib/feature-flags.ts, client/src/components/connectors/ConnectorRegistry.ts
Wrapped auth page in ConnectorAuthGuard; added isCloudBeesEnabled feature flag helper; gated connector registration on feature flag.
Documentation and Skills
server/chat/backend/agent/skills/integrations/cloudbees/SKILL.md, server/chat/backend/agent/skills/integrations/cloudbees_oc/SKILL.md, website/docs/integrations/cloudbees.md
Updated CloudBees skill to v2.0 (removed trace_context); added cloudbees_oc skill for enterprise actions; added integration docs covering connection modes, webhook setup, RCA actions, feature-flag correlation, and auto-trigger behavior.
Environment Configuration
.env.example
Documented NEXT_PUBLIC_ENABLE_CLOUDBEES feature flag.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Arvo-AI/aurora#437: Both PRs modify cloudbees_rca_tool.py to add/use CloudBees connection gating and extend the RCA tool with enterprise actions backed by dedicated connector clients.
  • Arvo-AI/aurora#97: The main PR removes the trace_context delegation from Jenkins RCA tool routing, directly impacting the Jenkins connector integration introduced in that PR.

Suggested reviewers

  • isiddharthsingh
  • OlivierTrudeau
  • damianloch

🐰 A rabbit's ode to CloudBees integration:

Cross controllers dance with glee,
Flags now flow from far and free,
Platform unites the enterprise way,
Aurora's reach grows every day! 🚀✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 72.73% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding enterprise support for Operations Center and Feature Management to the CloudBees connector.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/cloudbees-enterprise

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.

@beng360 beng360 marked this pull request as draft June 2, 2026 21:08
Comment thread server/chat/backend/agent/tools/cloudbees_rca_tool.py Fixed
Comment thread server/chat/backend/agent/tools/cloudbees_rca_tool.py Fixed
Comment thread server/chat/backend/agent/tools/cloudbees_rca_tool.py Fixed
Comment thread server/connectors/cloudbees_connector/oc_client.py Fixed
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: 5

🤖 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 `@server/chat/backend/agent/tools/cloudbees_rca_tool.py`:
- Around line 157-171: The code returns a field named "warnings" mapped from the
variable error after calling oc_client.query_recent_builds_across_controllers,
which is confusing; update this by either changing the client's return name or
mapping the returned error value to a clearer local name before building the
JSON. Specifically, modify the call/response handling around
query_recent_builds_across_controllers (the tuple currently unpacked as success,
builds, error) so that the third element is named warnings (or warnings_list)
and use that when returning {"builds": builds, "count": len(builds),
"time_window_hours": time_window_hours, "warnings": warnings}, or alternatively
change the client function signature to return (success, builds, warnings)
consistently; keep oc_client.close() in the finally block unchanged.

In `@server/connectors/cloudbees_connector/fm_client.py`:
- Around line 81-89: The retry branch after receiving a 429 does not update the
rate limiter state, so update _last_request_time (or call _rate_limit_wait())
after the successful retry to reflect the retry's timestamp; specifically, in
the block around the second client.request (the retry using
client.request(method=method, url=url, params=params)), ensure you update the
module/class attribute _last_request_time (or invoke _rate_limit_wait())
immediately after the retry response is obtained and before returning or
proceeding, and preserve existing 429 handling (i.e., still return the same
error if the retry is 429).

In `@server/connectors/cloudbees_connector/oc_client.py`:
- Around line 175-231: The time_window_hours parameter in
query_recent_builds_across_controllers is unused; filter builds by timestamp
before returning them. After collecting builds into all_builds (and before
sorting), compute a cutoff = current_time_ms - time_window_hours * 3600 * 1000
and keep only builds where build.get("timestamp", 0) >= cutoff; ensure you
handle None/missing timestamps by excluding them, preserve existing
controller/job annotations (_controller, _job), and then sort and return as
before (using MAX_CONTROLLERS and MAX_BUILDS_PER_CONTROLLER unchanged).

In `@server/routes/cloudbees/cloudbees_routes.py`:
- Around line 366-368: The code returns the raw error from
oc_client.discover_controllers() directly in the HTTP response (the error
variable used in jsonify), which can leak sensitive info; change the response to
sanitize or replace the error with a safe message (e.g., "Failed to discover
controllers") and log the original error internally using the existing logger
before returning the generic message. Update the block that checks success from
oc_client.discover_controllers() to: log the original error
(logger.error/exception with the error variable), set a sanitized_error
string/fallback, and return jsonify({"connected": True, "controllers": [],
"error": sanitized_error}), 502 instead of returning the raw error.
- Around line 268-270: The handler currently returns the raw error from
oc_client.get_server_info() (variables success, error, user_id); change it to
use the same safe_errors whitelist logic used in the /connect route: if error is
in safe_errors return that message to the client, otherwise return a generic
"Failed to validate Operations Center credentials" message while keeping the
full error in the server log via logger.warning; ensure you reference the
safe_errors set and apply the check before building the jsonify response.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: 6bf9f2d1-257b-425e-8746-5503dea60749

📥 Commits

Reviewing files that changed from the base of the PR and between ffd1e30 and 43e4f46.

📒 Files selected for processing (6)
  • server/chat/backend/agent/tools/cloudbees_rca_tool.py
  • server/connectors/cloudbees_connector/__init__.py
  • server/connectors/cloudbees_connector/fm_client.py
  • server/connectors/cloudbees_connector/oc_client.py
  • server/connectors/cloudbees_connector/platform_client.py
  • server/routes/cloudbees/cloudbees_routes.py

Comment thread server/chat/backend/agent/tools/cloudbees_rca_tool.py Outdated
Comment thread server/connectors/cloudbees_connector/fm_client.py
Comment thread server/connectors/cloudbees_connector/oc_client.py
Comment thread server/routes/cloudbees/cloudbees_routes.py Outdated
Comment thread server/routes/cloudbees/cloudbees_routes.py Outdated
Comment thread client/src/app/cloudbees/auth/components/CloudBeesAuthPage.tsx Fixed
Comment thread client/src/app/cloudbees/auth/components/CloudBeesAuthPage.tsx Fixed
Comment thread client/src/app/cloudbees/auth/components/CloudBeesAuthPage.tsx Fixed
Comment thread client/src/app/cloudbees/auth/components/CloudBeesAuthPage.tsx Fixed
Comment thread client/src/app/cloudbees/auth/components/CloudBeesAuthPage.tsx Fixed
@beng360 beng360 marked this pull request as ready for review June 4, 2026 20:22
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: 6

Caution

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

⚠️ Outside diff range comments (3)
client/src/hooks/use-github-status.ts (1)

120-120: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove dead cleanup code for non-existent focus event listener.

Line 120 removes a focus event listener that is never added in this effect (lines 114-116 show only providerStateChanged, message, and visibilitychange are registered). This cleanup is dead code left from incomplete refactoring.

🧹 Proposed fix
     return () => {
       window.removeEventListener('providerStateChanged', handleProviderChange);
       window.removeEventListener('message', handleAuthMessage);
-      window.removeEventListener('focus', checkStatus);
       document.removeEventListener('visibilitychange', handleVisibility);
     };
🤖 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 `@client/src/hooks/use-github-status.ts` at line 120, The cleanup in the effect
contains a stale call removing a 'focus' listener that is never added; remove
the dead window.removeEventListener('focus', checkStatus) line and ensure the
effect's cleanup only unregisters the actual listeners that were registered
(e.g., providerStateChanged, the message handler, and 'visibilitychange') so
that removeEventListener targets match the corresponding addEventListener calls
in use-github-status (look for the providerStateChanged and checkStatus/message
handler registrations).
client/src/components/github-provider-integration.tsx (1)

314-321: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Fix GitHub metadata regeneration/pending UX (no-op polling still called)

In client/src/components/github-provider-integration.tsx (lines 314-321), startMetadataPolling immediately returns after clearing pollingRef (disabled polling). It’s still invoked:

  • from loadSavedRepos (around line 329) after fetching selections
  • from handleRegenerate (around line 551) after setting metadata_status: 'generating'

Since the /repo-metadata/generate endpoint enqueues async work and only updates connected_repos.metadata_status in the DB, the client never re-fetches repo-selections to transition repo metadata_status from pending/generating to ready—so users will likely see “Generating description…” until they manually refresh/reconnect.

Either re-enable a lightweight revalidation/polling for GitHub (like the GitLab connect page does), or remove startMetadataPolling and its call sites and update the UI to reflect that only manual refresh will show completion. Also keep the issue #471`` rationale scoped to dev-only if that’s truly the intent.

🤖 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 `@client/src/components/github-provider-integration.tsx` around lines 314 -
321, startMetadataPolling currently clears pollingRef then returns, so callers
(startMetadataPolling invoked from loadSavedRepos and handleRegenerate) never
revalidate repo selections after triggering repo-metadata/generate, leaving
connected_repos.metadata_status stuck in pending/generating; either re-enable a
lightweight revalidation loop or remove the no-op and update callers/UI. Fix:
modify startMetadataPolling to implement a lightweight polling/revalidation
(e.g., setInterval that fetches repo selections every N seconds and clears
itself when no repos are pending) or delete startMetadataPolling and remove its
invocations in loadSavedRepos and handleRegenerate and ensure the UI for
metadata_status explicitly indicates manual refresh is required; reference
startMetadataPolling, loadSavedRepos, handleRegenerate, and
connected_repos.metadata_status to locate changes and keep the issue `#471`
dev-only rationale if you retain a disabled path.
server/routes/cloudbees/cloudbees_routes.py (1)

344-350: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

PAT/Bearer connections are misreported as disconnected in /platform-status.

Line 345 requires username, but PAT mode intentionally stores OC credentials with an empty username. Valid bearer connections will show connected: false.

Suggested fix
-    if oc_creds and oc_creds.get("base_url") and oc_creds.get("username") and oc_creds.get("api_token"):
+    if oc_creds and oc_creds.get("base_url") and oc_creds.get("api_token"):
+        auth_mode = (oc_creds.get("auth_mode") or "basic").lower()
+        has_required_identity = auth_mode == "bearer" or bool(oc_creds.get("username"))
+        if not has_required_identity:
+            return jsonify({
+                "operations_center": {"connected": False},
+                "feature_management": fm_status,
+            })
         oc_status = {
             "connected": True,
             "url": oc_creds["base_url"],
             "username": oc_creds["username"],
         }
🤖 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 `@server/routes/cloudbees/cloudbees_routes.py` around lines 344 - 350, The code
is incorrectly requiring a non-empty username for OC PAT/bearer auth, causing
valid bearer connections to appear disconnected; update the condition that
builds oc_status (where oc_creds is returned from
get_token_data(CLOUDBEES_OC_PROVIDER)) to consider the credentials "connected"
when base_url and api_token are present even if username is empty, and populate
oc_status["username"] using oc_creds.get("username") (which may be None or
empty) rather than gate connection on it; keep using oc_creds["base_url"] and
oc_creds["api_token"] for validation so bearer/PAT users are reported connected.
♻️ Duplicate comments (1)
server/connectors/cloudbees_connector/oc_client.py (1)

208-264: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

time_window_hours is still ignored in cross-controller queries.

Line 209 accepts time_window_hours, but returned builds are never filtered by cutoff before sorting/return. This causes stale build data in enterprise RCA results.

Suggested fix
+from datetime import datetime, timedelta, timezone
...
         # Sort by timestamp descending
+        if time_window_hours > 0:
+            cutoff_ms = int(
+                (datetime.now(timezone.utc) - timedelta(hours=time_window_hours)).timestamp() * 1000
+            )
+            all_builds = [
+                b for b in all_builds
+                if isinstance(b.get("timestamp"), (int, float)) and b.get("timestamp", 0) >= cutoff_ms
+            ]
+
         all_builds.sort(key=lambda b: b.get("timestamp", 0), reverse=True)
🤖 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 `@server/connectors/cloudbees_connector/oc_client.py` around lines 208 - 264,
query_recent_builds_across_controllers currently accepts time_window_hours but
never applies it; compute a cutoff (e.g., cutoff_ms = int(time.time() * 1000) -
time_window_hours * 3600 * 1000) and filter all_builds to only include builds
with build.get("timestamp", 0) >= cutoff_ms before sorting/returning; ensure
time is imported (or use time.time()) and keep the filtering step just prior to
the existing sort to avoid changing job/build-fetch logic inside
get_controller_client/list_builds.
🤖 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 `@client/src/app/cloudbees/auth/components/CloudBeesAuthPage.tsx`:
- Around line 109-111: The fetch response mapping in the CloudBeesAuthPage
component is using the wrong path; update the handler for
fetch("/api/cloudbees/status?full=true") so it checks and uses d?.summary (not
d?.status?.summary") when calling setSummary, i.e. replace the nested status
access with the top-level summary in the .then callback that currently invokes
setSummary.
- Around line 336-343: The success UI is shown even when the PUT to
/api/cloudbees/rca-settings fails because non-2xx responses aren't checked;
update the fetch handling in the CloudBeesAuthPage component to inspect the
fetch response (e.g., response.ok) after the await
fetch("/api/cloudbees/rca-settings", ...) call and only call
setRcaEnabled(checked) and toast(...) when the response is OK; if not OK, throw
or handle the error and show an error toast instead so the toggle UI doesn't
reflect an unpersisted change.
- Around line 144-145: Replace the generic CustomEvent("providerStateChanged")
dispatches with the CloudBees-specific event required by the connector contract:
dispatch new Event("cloudBeesStateChanged") (use Event, not CustomEvent). Update
each dispatch site in CloudBeesAuthPage.tsx where window.dispatchEvent(new
CustomEvent("providerStateChanged")) is called so they instead call
window.dispatchEvent(new Event("cloudBeesStateChanged")), ensuring disconnect
and status revalidation emit the provider-specific event name.

In `@client/src/hooks/use-github-status.ts`:
- Around line 111-116: Remove the no-op visibility handler and its registration:
delete the empty handleVisibility function and the
document.addEventListener('visibilitychange', handleVisibility) line so no
unused listener is attached; ensure only the remaining listeners
(window.addEventListener('providerStateChanged', handleProviderChange') and
window.addEventListener('message', handleAuthMessage') remain unchanged to
preserve behavior.

In `@server/connectors/cloudbees_connector/oc_client.py`:
- Around line 73-97: The current host-comparison in the controller validation
(variables oc_host, ctrl_host, oc_domain, ctrl_domain) is insecure because it
simply joins trailing labels and can be bypassed (e.g., co.uk or IP octet
suffixes); replace this heuristic with a robust check: parse both hosts, treat
IP literals separately by using ipaddress.ip_address and require exact IP
equality for IPs, and for domain names use a public suffix list-based extraction
(e.g., tldextract or publicsuffix2) to get the registrable/registered domain for
self.base_url and controller_url and compare those registered domains for
equality; ensure you raise the same ValueError when they do not match and keep
the invalid-URL guard for empty ctrl_host.

In `@server/routes/cloudbees/cloudbees_routes.py`:
- Around line 270-274: The CloudBeesOCClient created in the route handlers
(instances in the CloudBeesOCClient creation blocks and used to call
get_server_info and similar methods around lines shown) are never closed; update
the handlers to ensure the client is always closed after use by wrapping usage
in a try/finally (or using the client's context manager if it implements
__enter__/__exit__) and call the client's close method (e.g., oc_client.close())
in the finally block; apply the same pattern to the other route path(s) where
CloudBeesOCClient is constructed (also the block around the other occurrence
noted) so connections/file descriptors are released under error and normal
execution.

---

Outside diff comments:
In `@client/src/components/github-provider-integration.tsx`:
- Around line 314-321: startMetadataPolling currently clears pollingRef then
returns, so callers (startMetadataPolling invoked from loadSavedRepos and
handleRegenerate) never revalidate repo selections after triggering
repo-metadata/generate, leaving connected_repos.metadata_status stuck in
pending/generating; either re-enable a lightweight revalidation loop or remove
the no-op and update callers/UI. Fix: modify startMetadataPolling to implement a
lightweight polling/revalidation (e.g., setInterval that fetches repo selections
every N seconds and clears itself when no repos are pending) or delete
startMetadataPolling and remove its invocations in loadSavedRepos and
handleRegenerate and ensure the UI for metadata_status explicitly indicates
manual refresh is required; reference startMetadataPolling, loadSavedRepos,
handleRegenerate, and connected_repos.metadata_status to locate changes and keep
the issue `#471` dev-only rationale if you retain a disabled path.

In `@client/src/hooks/use-github-status.ts`:
- Line 120: The cleanup in the effect contains a stale call removing a 'focus'
listener that is never added; remove the dead
window.removeEventListener('focus', checkStatus) line and ensure the effect's
cleanup only unregisters the actual listeners that were registered (e.g.,
providerStateChanged, the message handler, and 'visibilitychange') so that
removeEventListener targets match the corresponding addEventListener calls in
use-github-status (look for the providerStateChanged and checkStatus/message
handler registrations).

In `@server/routes/cloudbees/cloudbees_routes.py`:
- Around line 344-350: The code is incorrectly requiring a non-empty username
for OC PAT/bearer auth, causing valid bearer connections to appear disconnected;
update the condition that builds oc_status (where oc_creds is returned from
get_token_data(CLOUDBEES_OC_PROVIDER)) to consider the credentials "connected"
when base_url and api_token are present even if username is empty, and populate
oc_status["username"] using oc_creds.get("username") (which may be None or
empty) rather than gate connection on it; keep using oc_creds["base_url"] and
oc_creds["api_token"] for validation so bearer/PAT users are reported connected.

---

Duplicate comments:
In `@server/connectors/cloudbees_connector/oc_client.py`:
- Around line 208-264: query_recent_builds_across_controllers currently accepts
time_window_hours but never applies it; compute a cutoff (e.g., cutoff_ms =
int(time.time() * 1000) - time_window_hours * 3600 * 1000) and filter all_builds
to only include builds with build.get("timestamp", 0) >= cutoff_ms before
sorting/returning; ensure time is imported (or use time.time()) and keep the
filtering step just prior to the existing sort to avoid changing job/build-fetch
logic inside get_controller_client/list_builds.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: dbdd80f0-6a98-4e52-9a20-a7f066b715b6

📥 Commits

Reviewing files that changed from the base of the PR and between 43e4f46 and f9abf72.

⛔ Files ignored due to path filters (1)
  • client/public/cloudbees.svg is excluded by !**/*.svg
📒 Files selected for processing (14)
  • client/src/app/api/cloudbees/connect-platform/route.ts
  • client/src/app/api/cloudbees/controllers/route.ts
  • client/src/app/api/cloudbees/platform-status/route.ts
  • client/src/app/cloudbees/auth/components/CloudBeesAuthPage.tsx
  • client/src/app/cloudbees/auth/page.tsx
  • client/src/components/github-provider-integration.tsx
  • client/src/hooks/use-github-status.ts
  • server/chat/backend/agent/skills/integrations/cloudbees/SKILL.md
  • server/chat/backend/agent/skills/integrations/cloudbees_oc/SKILL.md
  • server/chat/backend/agent/tools/cloud_tools.py
  • server/chat/background/prediscovery_task.py
  • server/connectors/cloudbees_connector/oc_client.py
  • server/routes/cloudbees/cloudbees_routes.py
  • website/docs/integrations/cloudbees.md

Comment thread client/src/app/cloudbees/auth/components/CloudBeesAuthPage.tsx
Comment thread client/src/app/cloudbees/auth/components/CloudBeesAuthPage.tsx
Comment thread client/src/app/cloudbees/auth/components/CloudBeesAuthPage.tsx Outdated
Comment thread client/src/hooks/use-github-status.ts
Comment thread server/connectors/cloudbees_connector/oc_client.py
Comment thread server/routes/cloudbees/cloudbees_routes.py Outdated
beng360 added a commit that referenced this pull request Jun 4, 2026
Backend:
- Use 'with' for OC/FM client context managers in RCA tool
- Remove unused 'time' import
- Update rate limit tracking after 429 retry
- Use time_window_hours parameter in cross-controller query
- Sanitize error messages in routes (no internal hostnames)
- Close OC clients properly in routes
- Strengthen domain matching (prevent evil-company.com bypass)

Frontend:
- Remove unused imports (Button, Badge, router)
- Fix status summary mapping
- Dispatch providerStateChanged after connect
- Only toast RCA toggle success on actual success
- Remove empty visibility handler

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment thread server/routes/cloudbees/cloudbees_routes.py Dismissed
beng360 and others added 17 commits June 4, 2026 17:00
…e support

- CloudBeesOCClient: discovers managed controllers, queries across them
- CloudBeesFMClient: queries feature flag changes for incident correlation
- CloudBeesPlatformClient: orchestration layer combining OC + FM
- Enhanced RCA tool: new actions (flag_changes, cross_controller_deployments, controller_list)
- New routes: /connect-platform, /platform-status, /controllers, /disconnect-platform
- Graceful degradation: missing credentials return helpful messages, not errors
- Additive: existing single-controller flow completely unchanged

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Path injection: URL-encode app_id/flag_name in FM client
- SSRF: validate controller URLs match OC domain before sending creds
- Resource leak: add context manager support + close() in action handlers
- Remove trace_context delegation to wrong provider
- Add single retry on FM 429 responses
- Cache platform_status instead of hitting FM API every call
- Sanitize error messages from OC (no raw exceptions to caller)
- Validate URL schemes (require http/https)
- Guard against empty app_id in flag_changes action

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Three connection modes in one clean flow:
- Single Controller: direct Jenkins/CloudBees CI connection (existing)
- Operations Center: multi-controller management with controller discovery
- Personal Access Token: platform-level auth for organizations using PATs

Optional Feature Management section under OC mode for flag correlation.
Displays discovered controllers after successful OC connection.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Critical fixes:
- Field name alignment: frontend now sends oc_url/api_token/fm_api_token (matches backend)
- PAT mode: backend now supports Bearer token auth when username is empty
- OC client: supports both Basic Auth and Bearer token modes
- Disconnect: removes both single-controller AND platform credentials
- Controllers: fetched on page load from /controllers endpoint
- Connect response: includes discovered controllers list

Other fixes:
- Client-side URL scheme validation (http/https only)
- 0 controllers: shows guidance message instead of dead-end
- Clear all form fields on disconnect
- Remove trace_context from tool description (action doesn't exist)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove all Card wrappers and bordered containers
- Mode picker: divider-separated list with hover arrows
- Forms: transparent inputs, rounded-xl, generous spacing
- Connected: minimal data display with status dots
- Feature management: collapsible <details> instead of card section
- Matches sign-in page typography and spacing philosophy
- No decorative elements, trusts the content

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- SKILL.md: document enterprise actions (flag_changes, cross_controller_deployments, controller_list)
- Add cloudbees_oc skill entry for SkillRegistry detection
- SSRF: relax validation to allow sibling subdomains (same registrable domain)
- Prediscovery: add OC exploration instructions to prompt
- MCP: verify cloudbees_rca is available via MCP tool registry

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Covers all three connection modes (Single Controller, Operations Center, PAT),
webhook setup, RCA actions (standard + enterprise), feature flag correlation,
and auto-trigger configuration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Complete redesign as a multi-step wizard:
- Step 1: Choose connection mode (OC, Single, PAT) with descriptive cards
- Step 2: Enter credentials with inline instruction box
- Step 3: Webhook setup with copy-able URL and Jenkinsfile snippet
- Connected: Controller list + 'what happens next' section
- Progress bar tracks position through the flow
- Full max-w-2xl width, generous spacing, no cramped forms

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… webhook, deployments)

Connected state is now a full dashboard:
- 4-column stats grid (jobs, nodes, executors, queue)
- Job health bar with colored segments + legend
- RCA auto-trigger toggle switch
- Webhook URL with copy + Jenkinsfile snippet (collapsible)
- Recent deployments list with status dots
- Controllers list (OC mode)
- All in the same dark minimal aesthetic

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Refs #471

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ctions

- Removed all trace_context references (action no longer exists)
- Flag_changes: explicitly states 'only call if FM is connected + have app_id'
- cross_controller_deployments: only if OC is connected
- Prevents agent from calling actions that will always error

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…DBEES

Hidden by default (.env.example=false). Set NEXT_PUBLIC_ENABLE_CLOUDBEES=true to show.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Backend:
- Use 'with' for OC/FM client context managers in RCA tool
- Remove unused 'time' import
- Update rate limit tracking after 429 retry
- Use time_window_hours parameter in cross-controller query
- Sanitize error messages in routes (no internal hostnames)
- Close OC clients properly in routes
- Strengthen domain matching (prevent evil-company.com bypass)

Frontend:
- Remove unused imports (Button, Badge, router)
- Fix status summary mapping
- Dispatch providerStateChanged after connect
- Only toast RCA toggle success on actual success
- Remove empty visibility handler

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@beng360 beng360 force-pushed the feat/cloudbees-enterprise branch from 9823bad to e5e5617 Compare June 4, 2026 21:01
…use HTTP)

Co-Authored-By: Claude Opus 4.6 <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.

Actionable comments posted: 8

♻️ Duplicate comments (1)
server/connectors/cloudbees_connector/oc_client.py (1)

71-108: ⚠️ Potential issue | 🟠 Major

Review: _validate_controller_url uses a fragile registrable-domain heuristic; switch to public-suffix extraction

server/connectors/cloudbees_connector/oc_client.py computes the “base domain” via a len(oc_parts[-2]) <= 3 heuristic and then allows controllers by suffix match. Repo-wide search shows no tldextract (or equivalent public-suffix/registrable-domain helper) used, so this approach isn’t robust for complex/multi-part public suffixes and can miscompute the allowlist boundary.

Use a public-suffix–based registrable-domain extractor (e.g., tldextract or another PSList-backed library) to derive the registrable domain, then perform the suffix check from that value.

🤖 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 `@server/connectors/cloudbees_connector/oc_client.py` around lines 71 - 108,
The current _validate_controller_url in oc_client.py uses a fragile heuristic on
oc_host parts to compute a base domain; replace that with a public-suffix-aware
registrable domain extractor (e.g., tldextract). Import and use
tldextract.extract(self.base_url_hostname).registered_domain (or similar API) to
derive oc_registrable_domain (fall back to oc_host when empty), and then
validate controller_url by parsing ctrl_host and ensuring either ctrl_host ==
oc_host or ctrl_host == oc_registrable_domain or ctrl_host.endswith("." +
oc_registrable_domain). Update _validate_controller_url to use these names
(_validate_controller_url, self.base_url, oc_host, ctrl_host) and add the
tldextract dependency to the project requirements.
🤖 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 `@client/src/app/cloudbees/auth/components/CloudBeesAuthPage.tsx`:
- Around line 966-975: The list rendering in CloudBeesAuthPage uses the array
index `i` as the React key which can break reconciliation; update the key on the
map over `deployments.slice(0, 5).map((d, i) => ...)` to use a stable unique
identifier from each deployment (e.g., `d.id` or `d.build_number` if `id` is not
available) instead of `i`, ensuring the outer div's `key` prop is changed to
that unique field so React can track items reliably.

In `@server/chat/backend/agent/tools/cloudbees_rca_tool.py`:
- Around line 136-167: Normalize and validate time_window_hours before calling
FM/OC methods: in the "flag_changes" and "cross_controller_deployments"
branches, check the time_window_hours variable (used when calling
fm_client.get_recent_flag_changes and
oc_client.query_recent_builds_across_controllers) and if it's None, non-numeric,
or <= 0 return a structured json error (e.g., {"error": "time_window_hours must
be a positive integer"}); alternatively coerce valid string/float input to an
int > 0 and use that value for the calls. Ensure this validation happens before
obtaining/using fm_client or oc_client so you don't open connections
unnecessarily.

In `@server/chat/background/prediscovery_task.py`:
- Around line 204-213: The preflight org lookup and hook invocation
(get_org_id_for_user(user_id) and get_hook("before_llm_call")(org_id, user_id))
run outside the task's try/except so exceptions bypass the function’s structured
failure path; move these calls into the existing try block (or wrap them in
their own try/except that funnels exceptions into the same failure return),
ensure logger.warning usage remains but that any exception from
get_org_id_for_user or get_hook is caught and returned as the function's
standard error response (e.g., {"status": "error", "error": ...}) so the task
always follows the structured failure handling.

In `@server/connectors/cloudbees_connector/fm_client.py`:
- Around line 106-108: Replace the logger.error call in the HTTP exception
handler that catches httpx.HTTPError with logger.exception so the traceback is
preserved; specifically, in the except httpx.HTTPError as e block in
fm_client.py (the Feature Management API request handler) change
logger.error("FM API request failed: %s", e) to logger.exception("FM API request
failed: %s", e) and keep the existing return False, None, "Feature Management
API request failed." so the error is logged with its stack trace while the
function behavior remains the same.

In `@server/connectors/cloudbees_connector/oc_client.py`:
- Line 263: The tuple returned by client.list_builds(...) unpacks into ok,
builds, err but err is never used; change the unpack to use a deliberately
ignored name (e.g., _err or _) so the unused variable is clearly intentional.
Update the call site in oc_client.py where client.list_builds(job_name,
limit=MAX_BUILDS_PER_CONTROLLER) is assigned (the ok, builds, err unpack) to use
ok, builds, _err (or ok, builds, _) instead, leaving the rest of the logic
unchanged.

In `@server/routes/cloudbees/cloudbees_routes.py`:
- Line 275: In the unpacking statements where oc_client.get_server_info() (and
similar calls at the other noted spots) return multiple values, rename the
unused variables server_data, ctrl_error, and error to _ (or _server_data/_error
if you prefer distinct discard names) so linters stop flagging them as unused;
update the unpacking near the oc_client.get_server_info() call and the analogous
unpackings referenced around the other occurrences to use the _ placeholder for
discarded values while keeping the used variable (e.g., success) intact.
- Around line 354-361: The platform-status check incorrectly requires
oc_creds.get("username") to be truthy which fails for PAT mode where username is
stored as an empty string; update the check in the block that reads oc_creds =
get_token_data(user_id, CLOUDBEES_OC_PROVIDER) to treat a missing/empty username
as valid for PAT by only requiring oc_creds["base_url"] and
oc_creds["api_token"] (or explicitly checking that "username" key exists even if
empty), and when building oc_status ensure you use oc_creds.get("username")
(allowing empty string) or fall back to None/empty string so OC shows connected
for PAT users.

In `@website/docs/integrations/cloudbees.md`:
- Around line 96-97: Update the documentation for the `flag_changes` tool to
state that `app_id` is required in addition to Feature Management; specifically
edit the `flag_changes` bullet to mention that callers must provide `app_id`
(e.g., "requires Feature Management and a valid app_id") so readers know to pass
`app_id` to avoid the immediate tool error when invoking `flag_changes`.

---

Duplicate comments:
In `@server/connectors/cloudbees_connector/oc_client.py`:
- Around line 71-108: The current _validate_controller_url in oc_client.py uses
a fragile heuristic on oc_host parts to compute a base domain; replace that with
a public-suffix-aware registrable domain extractor (e.g., tldextract). Import
and use tldextract.extract(self.base_url_hostname).registered_domain (or similar
API) to derive oc_registrable_domain (fall back to oc_host when empty), and then
validate controller_url by parsing ctrl_host and ensuring either ctrl_host ==
oc_host or ctrl_host == oc_registrable_domain or ctrl_host.endswith("." +
oc_registrable_domain). Update _validate_controller_url to use these names
(_validate_controller_url, self.base_url, oc_host, ctrl_host) and add the
tldextract dependency to the project requirements.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: 60a1b57e-dafb-4510-9c4f-50e1f6feca4a

📥 Commits

Reviewing files that changed from the base of the PR and between f9abf72 and e5e5617.

⛔ Files ignored due to path filters (1)
  • client/public/cloudbees.svg is excluded by !**/*.svg
📒 Files selected for processing (21)
  • .env.example
  • client/src/app/api/cloudbees/connect-platform/route.ts
  • client/src/app/api/cloudbees/controllers/route.ts
  • client/src/app/api/cloudbees/platform-status/route.ts
  • client/src/app/cloudbees/auth/components/CloudBeesAuthPage.tsx
  • client/src/app/cloudbees/auth/page.tsx
  • client/src/components/connectors/ConnectorRegistry.ts
  • client/src/components/github-provider-integration.tsx
  • client/src/hooks/use-github-status.ts
  • client/src/lib/feature-flags.ts
  • server/chat/backend/agent/skills/integrations/cloudbees/SKILL.md
  • server/chat/backend/agent/skills/integrations/cloudbees_oc/SKILL.md
  • server/chat/backend/agent/tools/cloud_tools.py
  • server/chat/backend/agent/tools/cloudbees_rca_tool.py
  • server/chat/background/prediscovery_task.py
  • server/connectors/cloudbees_connector/__init__.py
  • server/connectors/cloudbees_connector/fm_client.py
  • server/connectors/cloudbees_connector/oc_client.py
  • server/connectors/cloudbees_connector/platform_client.py
  • server/routes/cloudbees/cloudbees_routes.py
  • website/docs/integrations/cloudbees.md
💤 Files with no reviewable changes (1)
  • client/src/hooks/use-github-status.ts

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.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 8

♻️ Duplicate comments (1)
server/connectors/cloudbees_connector/oc_client.py (1)

71-108: ⚠️ Potential issue | 🟠 Major

Review: _validate_controller_url uses a fragile registrable-domain heuristic; switch to public-suffix extraction

server/connectors/cloudbees_connector/oc_client.py computes the “base domain” via a len(oc_parts[-2]) <= 3 heuristic and then allows controllers by suffix match. Repo-wide search shows no tldextract (or equivalent public-suffix/registrable-domain helper) used, so this approach isn’t robust for complex/multi-part public suffixes and can miscompute the allowlist boundary.

Use a public-suffix–based registrable-domain extractor (e.g., tldextract or another PSList-backed library) to derive the registrable domain, then perform the suffix check from that value.

🤖 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 `@server/connectors/cloudbees_connector/oc_client.py` around lines 71 - 108,
The current _validate_controller_url in oc_client.py uses a fragile heuristic on
oc_host parts to compute a base domain; replace that with a public-suffix-aware
registrable domain extractor (e.g., tldextract). Import and use
tldextract.extract(self.base_url_hostname).registered_domain (or similar API) to
derive oc_registrable_domain (fall back to oc_host when empty), and then
validate controller_url by parsing ctrl_host and ensuring either ctrl_host ==
oc_host or ctrl_host == oc_registrable_domain or ctrl_host.endswith("." +
oc_registrable_domain). Update _validate_controller_url to use these names
(_validate_controller_url, self.base_url, oc_host, ctrl_host) and add the
tldextract dependency to the project requirements.
🤖 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 `@client/src/app/cloudbees/auth/components/CloudBeesAuthPage.tsx`:
- Around line 966-975: The list rendering in CloudBeesAuthPage uses the array
index `i` as the React key which can break reconciliation; update the key on the
map over `deployments.slice(0, 5).map((d, i) => ...)` to use a stable unique
identifier from each deployment (e.g., `d.id` or `d.build_number` if `id` is not
available) instead of `i`, ensuring the outer div's `key` prop is changed to
that unique field so React can track items reliably.

In `@server/chat/backend/agent/tools/cloudbees_rca_tool.py`:
- Around line 136-167: Normalize and validate time_window_hours before calling
FM/OC methods: in the "flag_changes" and "cross_controller_deployments"
branches, check the time_window_hours variable (used when calling
fm_client.get_recent_flag_changes and
oc_client.query_recent_builds_across_controllers) and if it's None, non-numeric,
or <= 0 return a structured json error (e.g., {"error": "time_window_hours must
be a positive integer"}); alternatively coerce valid string/float input to an
int > 0 and use that value for the calls. Ensure this validation happens before
obtaining/using fm_client or oc_client so you don't open connections
unnecessarily.

In `@server/chat/background/prediscovery_task.py`:
- Around line 204-213: The preflight org lookup and hook invocation
(get_org_id_for_user(user_id) and get_hook("before_llm_call")(org_id, user_id))
run outside the task's try/except so exceptions bypass the function’s structured
failure path; move these calls into the existing try block (or wrap them in
their own try/except that funnels exceptions into the same failure return),
ensure logger.warning usage remains but that any exception from
get_org_id_for_user or get_hook is caught and returned as the function's
standard error response (e.g., {"status": "error", "error": ...}) so the task
always follows the structured failure handling.

In `@server/connectors/cloudbees_connector/fm_client.py`:
- Around line 106-108: Replace the logger.error call in the HTTP exception
handler that catches httpx.HTTPError with logger.exception so the traceback is
preserved; specifically, in the except httpx.HTTPError as e block in
fm_client.py (the Feature Management API request handler) change
logger.error("FM API request failed: %s", e) to logger.exception("FM API request
failed: %s", e) and keep the existing return False, None, "Feature Management
API request failed." so the error is logged with its stack trace while the
function behavior remains the same.

In `@server/connectors/cloudbees_connector/oc_client.py`:
- Line 263: The tuple returned by client.list_builds(...) unpacks into ok,
builds, err but err is never used; change the unpack to use a deliberately
ignored name (e.g., _err or _) so the unused variable is clearly intentional.
Update the call site in oc_client.py where client.list_builds(job_name,
limit=MAX_BUILDS_PER_CONTROLLER) is assigned (the ok, builds, err unpack) to use
ok, builds, _err (or ok, builds, _) instead, leaving the rest of the logic
unchanged.

In `@server/routes/cloudbees/cloudbees_routes.py`:
- Line 275: In the unpacking statements where oc_client.get_server_info() (and
similar calls at the other noted spots) return multiple values, rename the
unused variables server_data, ctrl_error, and error to _ (or _server_data/_error
if you prefer distinct discard names) so linters stop flagging them as unused;
update the unpacking near the oc_client.get_server_info() call and the analogous
unpackings referenced around the other occurrences to use the _ placeholder for
discarded values while keeping the used variable (e.g., success) intact.
- Around line 354-361: The platform-status check incorrectly requires
oc_creds.get("username") to be truthy which fails for PAT mode where username is
stored as an empty string; update the check in the block that reads oc_creds =
get_token_data(user_id, CLOUDBEES_OC_PROVIDER) to treat a missing/empty username
as valid for PAT by only requiring oc_creds["base_url"] and
oc_creds["api_token"] (or explicitly checking that "username" key exists even if
empty), and when building oc_status ensure you use oc_creds.get("username")
(allowing empty string) or fall back to None/empty string so OC shows connected
for PAT users.

In `@website/docs/integrations/cloudbees.md`:
- Around line 96-97: Update the documentation for the `flag_changes` tool to
state that `app_id` is required in addition to Feature Management; specifically
edit the `flag_changes` bullet to mention that callers must provide `app_id`
(e.g., "requires Feature Management and a valid app_id") so readers know to pass
`app_id` to avoid the immediate tool error when invoking `flag_changes`.

---

Duplicate comments:
In `@server/connectors/cloudbees_connector/oc_client.py`:
- Around line 71-108: The current _validate_controller_url in oc_client.py uses
a fragile heuristic on oc_host parts to compute a base domain; replace that with
a public-suffix-aware registrable domain extractor (e.g., tldextract). Import
and use tldextract.extract(self.base_url_hostname).registered_domain (or similar
API) to derive oc_registrable_domain (fall back to oc_host when empty), and then
validate controller_url by parsing ctrl_host and ensuring either ctrl_host ==
oc_host or ctrl_host == oc_registrable_domain or ctrl_host.endswith("." +
oc_registrable_domain). Update _validate_controller_url to use these names
(_validate_controller_url, self.base_url, oc_host, ctrl_host) and add the
tldextract dependency to the project requirements.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: 60a1b57e-dafb-4510-9c4f-50e1f6feca4a

📥 Commits

Reviewing files that changed from the base of the PR and between f9abf72 and e5e5617.

⛔ Files ignored due to path filters (1)
  • client/public/cloudbees.svg is excluded by !**/*.svg
📒 Files selected for processing (21)
  • .env.example
  • client/src/app/api/cloudbees/connect-platform/route.ts
  • client/src/app/api/cloudbees/controllers/route.ts
  • client/src/app/api/cloudbees/platform-status/route.ts
  • client/src/app/cloudbees/auth/components/CloudBeesAuthPage.tsx
  • client/src/app/cloudbees/auth/page.tsx
  • client/src/components/connectors/ConnectorRegistry.ts
  • client/src/components/github-provider-integration.tsx
  • client/src/hooks/use-github-status.ts
  • client/src/lib/feature-flags.ts
  • server/chat/backend/agent/skills/integrations/cloudbees/SKILL.md
  • server/chat/backend/agent/skills/integrations/cloudbees_oc/SKILL.md
  • server/chat/backend/agent/tools/cloud_tools.py
  • server/chat/backend/agent/tools/cloudbees_rca_tool.py
  • server/chat/background/prediscovery_task.py
  • server/connectors/cloudbees_connector/__init__.py
  • server/connectors/cloudbees_connector/fm_client.py
  • server/connectors/cloudbees_connector/oc_client.py
  • server/connectors/cloudbees_connector/platform_client.py
  • server/routes/cloudbees/cloudbees_routes.py
  • website/docs/integrations/cloudbees.md
💤 Files with no reviewable changes (1)
  • client/src/hooks/use-github-status.ts
🛑 Comments failed to post (8)
client/src/app/cloudbees/auth/components/CloudBeesAuthPage.tsx (1)

966-975: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Use a stable key instead of array index for deployment list items.

Using array index i as the React key can cause rendering issues if the list is reordered or items are removed. If deployments have a unique id field, use that instead.

🔧 Proposed fix
-                {deployments.slice(0, 5).map((d, i) => (
-                  <div key={i} className="flex items-center justify-between py-2.5 px-4 rounded-xl bg-white/[0.02]">
+                {deployments.slice(0, 5).map((d) => (
+                  <div key={d.id || `${d.service}-${d.build_number}`} className="flex items-center justify-between py-2.5 px-4 rounded-xl bg-white/[0.02]">
🧰 Tools
🪛 GitHub Check: SonarCloud Code Analysis

[warning] 967-967: Do not use Array index in keys

See more on https://sonarcloud.io/project/issues?id=Arvo-AI_aurora&issues=AZ6TuptqKPEJmiI9QU_1&open=AZ6TuptqKPEJmiI9QU_1&pullRequest=468

🤖 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 `@client/src/app/cloudbees/auth/components/CloudBeesAuthPage.tsx` around lines
966 - 975, The list rendering in CloudBeesAuthPage uses the array index `i` as
the React key which can break reconciliation; update the key on the map over
`deployments.slice(0, 5).map((d, i) => ...)` to use a stable unique identifier
from each deployment (e.g., `d.id` or `d.build_number` if `id` is not available)
instead of `i`, ensuring the outer div's `key` prop is changed to that unique
field so React can track items reliably.
server/chat/backend/agent/tools/cloudbees_rca_tool.py (1)

136-167: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Normalize and validate time_window_hours before OC/FM calls.

Line 149 and Line 163 pass time_window_hours straight into methods that do numeric time math. If None (or non-positive) arrives, these branches can fail at runtime instead of returning a structured tool error.

💡 Suggested patch
 def cloudbees_rca(
@@
     user_id = kwargs.get("user_id", "")
@@
+    window_hours = (
+        time_window_hours
+        if isinstance(time_window_hours, int) and time_window_hours > 0
+        else 24
+    )
+
     # --- Enterprise actions (OC / FM) ---
     if action == "flag_changes":
@@
             success, changes, error = fm_client.get_recent_flag_changes(
-                app_id, since_hours=time_window_hours
+                app_id, since_hours=window_hours
             )
@@
-            return json.dumps({"flag_changes": changes, "count": len(changes), "time_window_hours": time_window_hours})
+            return json.dumps({"flag_changes": changes, "count": len(changes), "time_window_hours": window_hours})
@@
             success, builds, error = oc_client.query_recent_builds_across_controllers(
-                service=service, time_window_hours=time_window_hours
+                service=service, time_window_hours=window_hours
             )
@@
-            return json.dumps({"builds": builds, "count": len(builds), "time_window_hours": time_window_hours, "warnings": error})
+            return json.dumps({"builds": builds, "count": len(builds), "time_window_hours": window_hours, "warnings": error})
🤖 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 `@server/chat/backend/agent/tools/cloudbees_rca_tool.py` around lines 136 -
167, Normalize and validate time_window_hours before calling FM/OC methods: in
the "flag_changes" and "cross_controller_deployments" branches, check the
time_window_hours variable (used when calling fm_client.get_recent_flag_changes
and oc_client.query_recent_builds_across_controllers) and if it's None,
non-numeric, or <= 0 return a structured json error (e.g., {"error":
"time_window_hours must be a positive integer"}); alternatively coerce valid
string/float input to an int > 0 and use that value for the calls. Ensure this
validation happens before obtaining/using fm_client or oc_client so you don't
open connections unnecessarily.
server/chat/background/prediscovery_task.py (1)

204-213: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Move hook preflight into guarded error handling.

Line 206 and Line 210 execute before the try block. If org lookup or hook resolution raises, the task exits without the function’s structured failure response path.

💡 Suggested patch
-    from utils.auth.stateless_auth import get_org_id_for_user
-    from datetime import datetime, timezone
-    org_id = get_org_id_for_user(user_id)
-
-    # Hook: check if LLM call is allowed
-    from utils.hooks import get_hook
-    hook_allowed, hook_message = get_hook("before_llm_call")(org_id, user_id)
-    if not hook_allowed:
-        logger.warning(f"[Prediscovery] Hook blocked for user {user_id}: {hook_message}")
-        return {"status": "hook_blocked", "error": hook_message}
-
     try:
+        from utils.auth.stateless_auth import get_org_id_for_user
+        from datetime import datetime, timezone
+        from utils.hooks import get_hook
+
+        org_id = get_org_id_for_user(user_id)
+        hook_allowed, hook_message = get_hook("before_llm_call")(org_id, user_id)
+        if not hook_allowed:
+            logger.warning("[Prediscovery] Hook blocked for user %s: %s", user_id, hook_message)
+            return {"status": "hook_blocked", "error": hook_message}
+
         providers = get_user_providers(user_id)
🧰 Tools
🪛 Ruff (0.15.15)

[warning] 212-212: Logging statement uses f-string

(G004)

🤖 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 `@server/chat/background/prediscovery_task.py` around lines 204 - 213, The
preflight org lookup and hook invocation (get_org_id_for_user(user_id) and
get_hook("before_llm_call")(org_id, user_id)) run outside the task's try/except
so exceptions bypass the function’s structured failure path; move these calls
into the existing try block (or wrap them in their own try/except that funnels
exceptions into the same failure return), ensure logger.warning usage remains
but that any exception from get_org_id_for_user or get_hook is caught and
returned as the function's standard error response (e.g., {"status": "error",
"error": ...}) so the task always follows the structured failure handling.
server/connectors/cloudbees_connector/fm_client.py (1)

106-108: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Use logging.exception to preserve stack trace.

When catching exceptions, logging.exception automatically includes the traceback, which aids debugging.

♻️ Proposed fix
         except httpx.HTTPError as e:
-            logger.error("FM API request failed: %s", e)
+            logger.exception("FM API request failed: %s", e)
             return False, None, "Feature Management API request failed."
🧰 Tools
🪛 GitHub Check: SonarCloud Code Analysis

[failure] 107-107: Use "logging.exception()" instead.

See more on https://sonarcloud.io/project/issues?id=Arvo-AI_aurora&issues=AZ6KKoSFRwyFzmiu2s3U&open=AZ6KKoSFRwyFzmiu2s3U&pullRequest=468

🪛 Ruff (0.15.15)

[warning] 107-107: Use logging.exception instead of logging.error

Replace with exception

(TRY400)

🤖 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 `@server/connectors/cloudbees_connector/fm_client.py` around lines 106 - 108,
Replace the logger.error call in the HTTP exception handler that catches
httpx.HTTPError with logger.exception so the traceback is preserved;
specifically, in the except httpx.HTTPError as e block in fm_client.py (the
Feature Management API request handler) change logger.error("FM API request
failed: %s", e) to logger.exception("FM API request failed: %s", e) and keep the
existing return False, None, "Feature Management API request failed." so the
error is logged with its stack trace while the function behavior remains the
same.
server/connectors/cloudbees_connector/oc_client.py (1)

263-263: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Unused variable err should be prefixed with underscore.

The err variable from client.list_builds() is unpacked but never used.

♻️ Proposed fix
-                    ok, builds, err = client.list_builds(job_name, limit=MAX_BUILDS_PER_CONTROLLER)
+                    ok, builds, _err = client.list_builds(job_name, limit=MAX_BUILDS_PER_CONTROLLER)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

                    ok, builds, _err = client.list_builds(job_name, limit=MAX_BUILDS_PER_CONTROLLER)
🧰 Tools
🪛 Ruff (0.15.15)

[warning] 263-263: Unpacked variable err is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)

🤖 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 `@server/connectors/cloudbees_connector/oc_client.py` at line 263, The tuple
returned by client.list_builds(...) unpacks into ok, builds, err but err is
never used; change the unpack to use a deliberately ignored name (e.g., _err or
_) so the unused variable is clearly intentional. Update the call site in
oc_client.py where client.list_builds(job_name, limit=MAX_BUILDS_PER_CONTROLLER)
is assigned (the ok, builds, err unpack) to use ok, builds, _err (or ok, builds,
_) instead, leaving the rest of the logic unchanged.
server/routes/cloudbees/cloudbees_routes.py (2)

275-275: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Rename unused unpacked variables to _ to satisfy linters.

Static analysis flags server_data, ctrl_error, and error as unused. Prefix them with _ to indicate intentional discard.

🔧 Proposed fix
-        success, server_data, error = oc_client.get_server_info()
+        success, _server_data, error = oc_client.get_server_info()
-            ctrl_success, ctrl_list, ctrl_error = oc_client.discover_controllers()
+            ctrl_success, ctrl_list, _ctrl_error = oc_client.discover_controllers()
-        success, controllers, error = oc_client.discover_controllers()
+        success, controllers, _error = oc_client.discover_controllers()

Also applies to: 305-305, 396-396

🧰 Tools
🪛 GitHub Check: SonarCloud Code Analysis

[warning] 275-275: Replace the unused local variable "server_data" with "_".

See more on https://sonarcloud.io/project/issues?id=Arvo-AI_aurora&issues=AZ6KKoLxRwyFzmiu2s3M&open=AZ6KKoLxRwyFzmiu2s3M&pullRequest=468

🪛 Ruff (0.15.15)

[warning] 275-275: Unpacked variable server_data is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)

🤖 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 `@server/routes/cloudbees/cloudbees_routes.py` at line 275, In the unpacking
statements where oc_client.get_server_info() (and similar calls at the other
noted spots) return multiple values, rename the unused variables server_data,
ctrl_error, and error to _ (or _server_data/_error if you prefer distinct
discard names) so linters stop flagging them as unused; update the unpacking
near the oc_client.get_server_info() call and the analogous unpackings
referenced around the other occurrences to use the _ placeholder for discarded
values while keeping the used variable (e.g., success) intact.

354-361: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

PAT mode connection will always report not connected in /platform-status.

Line 356 requires oc_creds.get("username") to be truthy, but PAT mode stores an empty string for username (Line 291). This causes platform status to incorrectly report OC as disconnected for PAT-authenticated users.

🔧 Proposed fix
     # Check OC credentials exist
     oc_creds = get_token_data(user_id, CLOUDBEES_OC_PROVIDER)
-    if oc_creds and oc_creds.get("base_url") and oc_creds.get("username") and oc_creds.get("api_token"):
+    if oc_creds and oc_creds.get("base_url") and oc_creds.get("api_token"):
+        # Note: username may be empty for PAT/bearer auth mode
         oc_status = {
             "connected": True,
             "url": oc_creds["base_url"],
-            "username": oc_creds["username"],
+            "username": oc_creds.get("username") or None,
         }
🤖 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 `@server/routes/cloudbees/cloudbees_routes.py` around lines 354 - 361, The
platform-status check incorrectly requires oc_creds.get("username") to be truthy
which fails for PAT mode where username is stored as an empty string; update the
check in the block that reads oc_creds = get_token_data(user_id,
CLOUDBEES_OC_PROVIDER) to treat a missing/empty username as valid for PAT by
only requiring oc_creds["base_url"] and oc_creds["api_token"] (or explicitly
checking that "username" key exists even if empty), and when building oc_status
ensure you use oc_creds.get("username") (allowing empty string) or fall back to
None/empty string so OC shows connected for PAT users.
website/docs/integrations/cloudbees.md (1)

96-97: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Document app_id as required for flag_changes.

The current bullet says Feature Management is required, but callers also need app_id to avoid an immediate tool error.

💡 Suggested patch
-- `flag_changes` — Recent feature flag toggles (requires Feature Management)
+- `flag_changes` — Recent feature flag toggles (requires Feature Management and `app_id`)
🤖 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 `@website/docs/integrations/cloudbees.md` around lines 96 - 97, Update the
documentation for the `flag_changes` tool to state that `app_id` is required in
addition to Feature Management; specifically edit the `flag_changes` bullet to
mention that callers must provide `app_id` (e.g., "requires Feature Management
and a valid app_id") so readers know to pass `app_id` to avoid the immediate
tool error when invoking `flag_changes`.

These belong in a separate PR (refs #471).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Jun 4, 2026

Quality Gate Failed Quality Gate failed

Failed conditions
2 Security Hotspots

See analysis details on SonarQube Cloud

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.

Caution

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

⚠️ Outside diff range comments (1)
server/routes/cloudbees/cloudbees_routes.py (1)

354-361: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

PAT mode connections incorrectly reported as disconnected.

The status check on line 356 requires oc_creds.get("username") to be truthy. However, PAT mode stores an empty string for username (line 291 in connect_platform), and "" evaluates to False in Python. This causes all PAT-authenticated OC connections to appear disconnected.

Proposed fix
     # Check OC credentials exist
     oc_creds = get_token_data(user_id, CLOUDBEES_OC_PROVIDER)
-    if oc_creds and oc_creds.get("base_url") and oc_creds.get("username") and oc_creds.get("api_token"):
+    if oc_creds and oc_creds.get("base_url") and oc_creds.get("api_token"):
+        # PAT mode stores empty username, so don't require it for connection status
         oc_status = {
             "connected": True,
             "url": oc_creds["base_url"],
-            "username": oc_creds["username"],
+            "username": oc_creds.get("username") or None,
+            "auth_mode": oc_creds.get("auth_mode", "basic"),
         }
🤖 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 `@server/routes/cloudbees/cloudbees_routes.py` around lines 354 - 361, The
connection check incorrectly treats an empty string username (used for PAT mode
in connect_platform) as falsy; update the conditional that builds oc_status to
accept an existing username key even if empty by replacing the truthiness check
oc_creds.get("username") with a presence/null check (e.g.,
oc_creds.get("username") is not None or "username" in oc_creds) so
PAT-authenticated OC connections with "" usernames are reported connected when
base_url and api_token exist.
🤖 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.

Outside diff comments:
In `@server/routes/cloudbees/cloudbees_routes.py`:
- Around line 354-361: The connection check incorrectly treats an empty string
username (used for PAT mode in connect_platform) as falsy; update the
conditional that builds oc_status to accept an existing username key even if
empty by replacing the truthiness check oc_creds.get("username") with a
presence/null check (e.g., oc_creds.get("username") is not None or "username" in
oc_creds) so PAT-authenticated OC connections with "" usernames are reported
connected when base_url and api_token exist.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 820ee76a-9b00-4286-b312-e096601cd482

📥 Commits

Reviewing files that changed from the base of the PR and between e5e5617 and eac5708.

📒 Files selected for processing (1)
  • server/routes/cloudbees/cloudbees_routes.py

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.

1 participant