Skip to content

Commit 1bd2057

Browse files
squizziclaudemergify[bot]
authored
fix(runner): make integrations behave more consistently in session (#1686)
This PR fixes issues with Integrations not being loaded into session consistently. In prior test runs against `main`, a configured Jira integration would rarely work often resulting in this type of behavior. <img width="1593" height="1047" alt="Screenshot 2026-06-15 at 1 14 21 PM" src="https://github.com/user-attachments/assets/da98199e-3b0f-461e-88da-cfbcca18029b" /> Following up to tell the agent that Jira MCP is indeed available and the integration is configured often resulted in it not knowing the integration was even available to it within session: <img width="1593" height="337" alt="Screenshot 2026-06-15 at 1 14 35 PM" src="https://github.com/user-attachments/assets/5db71adc-6760-4e01-b9aa-2b1c046efeec" /> Following this change Jira (and other integrations) work on the first invocation of the prompt: <img width="1593" height="589" alt="Screenshot 2026-06-15 at 1 13 51 PM" src="https://github.com/user-attachments/assets/dc669252-cf51-4fd1-8aae-485a6ef7f8b1" /> Relates to: #1506 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added Jira auto-configuration for MCP when credentials are provided via environment variables. * System prompts now generate integration-specific guidance dynamically, including conditional integration status. * **Improvements** * Enhanced Claude first-run initialization to rebuild MCP servers and the Claude system prompt, ensuring the updated prompt is applied. * Git push instructions now adapt based on detected GitHub mode and credential/MCP configuration. * **Tests** * Added unit tests covering Claude run initialization and integration prompt generation across supported providers. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Kyle Squizzato <kysquizz@redhat.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
1 parent b85feee commit 1bd2057

5 files changed

Lines changed: 484 additions & 44 deletions

File tree

components/runners/ambient-runner/ambient_runner/bridges/claude/bridge.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -218,12 +218,16 @@ async def _initialize_run(
218218
await populate_mcp_server_credentials(self._context)
219219
self._last_creds_refresh = time.monotonic()
220220

221-
# If the caller changed, destroy the worker and rebuild MCP servers +
222-
# adapter so the new ClaudeSDKClient gets fresh mcp_servers config.
223-
# The session ID is preserved — --resume works because each SDK client
224-
# is a new CLI subprocess that spawns fresh MCP servers from os.environ.
221+
# Rebuild MCP servers when credentials may have changed.
222+
# On first run: always rebuild after credential fetch so credential-based servers
223+
# (e.g. Jira via session endpoint) that were missed during _setup_platform().
224+
# On user change: destroy worker so the new SDK client picks up fresh creds.
225225
user_changed = current_user_id != prev_user
226-
if user_changed and self._session_manager.get_existing(thread_id):
226+
if self._first_run:
227+
self._rebuild_mcp_servers()
228+
self._rebuild_system_prompt()
229+
self._adapter = None
230+
elif user_changed and self._session_manager.get_existing(thread_id):
227231
logger.info(
228232
f"User changed for thread={thread_id}, "
229233
"rebuilding MCP servers and adapter with new credentials"
@@ -719,6 +723,21 @@ def _rebuild_mcp_servers(self) -> None:
719723
self._allowed_tools = build_allowed_tools(self._mcp_servers)
720724
logger.info("Rebuilt MCP servers with updated credentials")
721725

726+
def _rebuild_system_prompt(self) -> None:
727+
"""Rebuild the system prompt with current env vars.
728+
729+
Called on first run after credential refresh so the prompt accurately
730+
reflects which integrations are configured (e.g. Jira via session
731+
endpoint). The initial build in _setup_platform() may have run before
732+
credentials were fully available.
733+
"""
734+
from ambient_runner.bridges.claude.prompts import build_sdk_system_prompt
735+
736+
self._system_prompt = build_sdk_system_prompt(
737+
self._context.workspace_path, self._cwd_path
738+
)
739+
logger.info("Rebuilt system prompt with updated credentials")
740+
722741
# ------------------------------------------------------------------
723742
# Private: adapter lifecycle
724743
# ------------------------------------------------------------------

components/runners/ambient-runner/ambient_runner/bridges/claude/mcp.py

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,24 @@ def build_mcp_servers(
140140
"Added credential MCP servers: %s", list(credential_mcp_servers.keys())
141141
)
142142

143+
# Fallback: add Jira MCP server from env vars when credentials are available but
144+
# no credential binding exists
145+
jira_server_name = _CREDENTIAL_MCP_REGISTRY["jira"]["server_name"]
146+
if (
147+
jira_server_name not in mcp_servers
148+
and os.getenv("JIRA_URL", "").strip()
149+
and os.getenv("JIRA_API_TOKEN", "").strip()
150+
):
151+
jira_entry = _CREDENTIAL_MCP_REGISTRY["jira"]
152+
mcp_servers[jira_server_name] = {
153+
"command": jira_entry["command"],
154+
"args": list(jira_entry["args"]),
155+
"env": {k: _expand_env_vars(v) for k, v in jira_entry["env"].items()},
156+
}
157+
logger.info(
158+
"Added Jira MCP server (credentials available via session endpoint)"
159+
)
160+
143161
# Gerrit MCP server (only if credentials are configured)
144162
gerrit_config = os.environ.get("GERRIT_CONFIG_PATH", "")
145163
if gerrit_config and Path(gerrit_config).exists():
@@ -243,11 +261,15 @@ def _build_sidecar_mcp_servers(credential_mcp_urls_raw: str) -> dict:
243261
try:
244262
credential_mcp_urls = json.loads(credential_mcp_urls_raw)
245263
except (json.JSONDecodeError, TypeError):
246-
logger.warning("Failed to parse CREDENTIAL_MCP_URLS — skipping credential MCP servers")
264+
logger.warning(
265+
"Failed to parse CREDENTIAL_MCP_URLS — skipping credential MCP servers"
266+
)
247267
return {}
248268

249269
if not isinstance(credential_mcp_urls, dict):
250-
logger.warning("CREDENTIAL_MCP_URLS is not a JSON object — skipping credential MCP servers")
270+
logger.warning(
271+
"CREDENTIAL_MCP_URLS is not a JSON object — skipping credential MCP servers"
272+
)
251273
return {}
252274

253275
servers: dict = {}
@@ -298,7 +320,11 @@ def _wait_for_sidecar_readiness(
298320
if not endpoints:
299321
return
300322

301-
logger.info("Waiting for %d credential sidecar(s) to become ready (timeout=%ds)", len(endpoints), int(timeout))
323+
logger.info(
324+
"Waiting for %d credential sidecar(s) to become ready (timeout=%ds)",
325+
len(endpoints),
326+
int(timeout),
327+
)
302328
deadline = time.monotonic() + timeout
303329
pending = list(endpoints)
304330

@@ -307,7 +333,9 @@ def _wait_for_sidecar_readiness(
307333
for name, host, port in pending:
308334
try:
309335
with socket.create_connection((host, port), timeout=1.0):
310-
logger.info("Credential sidecar %s ready at %s:%d", name, host, port)
336+
logger.info(
337+
"Credential sidecar %s ready at %s:%d", name, host, port
338+
)
311339
except (ConnectionRefusedError, OSError, socket.timeout):
312340
still_pending.append((name, host, port))
313341
pending = still_pending
@@ -316,7 +344,9 @@ def _wait_for_sidecar_readiness(
316344

317345
if pending:
318346
names = [p[0] for p in pending]
319-
logger.warning("Credential sidecar(s) not ready after %ds: %s", int(timeout), names)
347+
logger.warning(
348+
"Credential sidecar(s) not ready after %ds: %s", int(timeout), names
349+
)
320350

321351

322352
def _build_subprocess_mcp_servers() -> dict:

components/runners/ambient-runner/ambient_runner/bridges/gemini_cli/system_prompt.py

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -117,16 +117,31 @@ def _build_system_prompt(cwd_path: str) -> str:
117117
"""Build the full system.md content string."""
118118
from ambient_runner.platform.config import get_repos_config, load_ambient_config
119119
from ambient_runner.platform.prompts import (
120-
GITHUB_TOKEN_PROMPT,
121-
GITLAB_TOKEN_PROMPT,
122120
GIT_PUSH_INSTRUCTIONS_BODY,
123121
GIT_PUSH_INSTRUCTIONS_HEADER,
122+
GIT_PUSH_MCP_STEPS,
124123
GIT_PUSH_STEPS,
125-
MCP_INTEGRATIONS_PROMPT,
126124
WORKSPACE_FIXED_PATHS_PROMPT,
125+
_build_integrations_prompt,
126+
_detect_github,
127127
)
128128
from ambient_runner.platform.utils import derive_workflow_name
129129

130+
# Detect GitHub mode once so the git-push step selection is consistent
131+
# with what _build_integrations_prompt() will tell the model to use.
132+
_cmu: dict = {}
133+
_cmu_raw = os.getenv("CREDENTIAL_MCP_URLS", "").strip()
134+
if _cmu_raw:
135+
try:
136+
import json as _json
137+
138+
_parsed = _json.loads(_cmu_raw)
139+
if isinstance(_parsed, dict):
140+
_cmu = _parsed
141+
except (ValueError, TypeError):
142+
pass
143+
github_mode = _detect_github(_cmu)
144+
130145
# Pull in Gemini's dynamically-built default sections via variable substitution.
131146
# These are expanded at runtime by the CLI — no static text to maintain.
132147
sections = [
@@ -173,7 +188,8 @@ def _build_system_prompt(cwd_path: str) -> str:
173188
sections.append(GIT_PUSH_INSTRUCTIONS_BODY)
174189
for r in auto_push:
175190
sections.append(f"- **repos/{r.get('name', 'unknown')}/**")
176-
sections.append(GIT_PUSH_STEPS.format(branch=branch))
191+
push_steps = GIT_PUSH_MCP_STEPS if github_mode == "mcp" else GIT_PUSH_STEPS
192+
sections.append(push_steps.format(branch=branch))
177193

178194
# ---- Workflow directory ----
179195
if active_workflow_url:
@@ -200,14 +216,8 @@ def _build_system_prompt(cwd_path: str) -> str:
200216
except Exception as exc:
201217
logger.warning("Could not list uploaded files in %s: %s", uploads, exc)
202218

203-
# ---- MCP integration hints ----
204-
sections.append(MCP_INTEGRATIONS_PROMPT)
205-
206-
# ---- Token visibility ----
207-
if os.getenv("GITHUB_TOKEN"):
208-
sections.append(GITHUB_TOKEN_PROMPT)
209-
if os.getenv("GITLAB_TOKEN"):
210-
sections.append(GITLAB_TOKEN_PROMPT)
219+
# ---- Integration status — conditional on actual credential state ----
220+
sections.append(_build_integrations_prompt())
211221

212222
# ---- Workflow custom instructions ----
213223
ambient_config: dict = {}

0 commit comments

Comments
 (0)