Last updated: 2026-06-23
A VS Code sidebar extension that surfaces Harness pipeline execution (CI, CD, STO, TI, AIDA, OPA, SSCA, CCM) directly in the editor, scoped to the developer's current git branch + commit SHA. Zero context-switching.
GitHub repo: github.com/harness/harness-vscode-extension
Entry point: src/extension.ts
Build: npm run compile → esbuild produces dist/extension.js + dist/webview.js + dist/webview.css
| File | Role |
|---|---|
src/extension.ts |
Activation, command registration, poller wiring |
src/config/configManager.ts |
Reads/writes PAT + identifiers; global vs workspace config; env var fallback |
src/auth/onboarding.ts |
2-phase onboarding: global (PAT+AccountID) → workspace (Org/Project); env var flow |
src/auth/envCredentials.ts |
Reads HARNESS_* environment variables for passwordless auth |
src/api/harnessClient.ts |
Typed fetch wrapper, injects auth headers |
src/api/logService.ts |
Log fetch (blob/download + stream fallback), ZIP parsing |
src/api/approvalService.ts |
Submits approve/reject via API |
src/api/rerunService.ts |
Re-runs a pipeline via the retry API (inputSet YAML + first-stage retryStages + runAllStages) |
src/api/abortService.ts |
Aborts a running execution via interrupt API (AbortAll / UserMarkedFailure) |
src/api/stoScan.ts |
Parses STO scanner vulnerability counts from the execution graph (no API call) |
src/api/userService.ts |
Fetches current user + checks group membership |
src/pipeline/pipelinePoller.ts |
Polls for pipeline execution updates; pauses when sidebar hidden/window unfocused |
src/pipeline/executionDispatcher.ts |
Fan-out to CI/CD/STO/TI/SSCA/OPA/CCM/AIDA/Approval modules |
src/ui/sidebarProvider.ts |
WebviewViewProvider — injects HTML, tracks visibility |
src/ui/webviewBridge.ts |
Typed message bus between extension host and webview |
src/ui/webview/main.ts |
All webview rendering (browser context, no vscode APIs) |
src/ui/webview/styles.css |
Dual-theme styles (simple + enhanced OKLCH) |
src/fme/fmeClient.ts |
Harness Feature Management Engine (FME) client using Split.io SDK |
src/logs/logEditorTab.ts |
Opens step logs in editor tabs with syntax highlighting |
src/ai/detector.ts |
Detects Claude Code CLI/Extension/Cursor and checks MCP configuration |
src/ai/mcpConfigurer.ts |
Writes Harness MCP server config to ~/.claude.json |
src/ai/launcher.ts |
Launches Claude Code CLI/Extension or Cursor with prompts |
src/ai/aidaChatPanel.ts |
Harness AI Chat panel — SSE streaming, markdown, history cards, session title header, split input + MCP pill, elicitation cards, pipeline-context chip, ⌘⇧H focus |
git branch + SHA
→ POST /pipeline/api/pipelines/execution/summary (branch filter)
→ SHA match client-side (supports short/full SHA)
→ GET /pipeline/api/pipelines/execution/v2/{planExecutionId}?renderFullBottomGraph=true
→ executionDispatcher inspects moduleInfo keys:
moduleInfo.ci → log fetch (blob/download or stream)
moduleInfo.cd → deployment status
moduleInfo.ti → test results + flaky → diagnostics
moduleInfo.ssca → SBOM component flags
moduleInfo.ccm → build cost
governanceMetadata → OPA policy evaluation
status=APPROVALWAITING → approval card with Approve/Reject buttons
Two-approach strategy in src/api/logService.ts:
-
Blob/download (preferred):
POST /gateway/log-service/blob/download→ signed URL → ZIP download → parse NDJSON- Requires FF
SPG_LOG_SERVICE_ENABLE_DOWNLOAD_LOGS
- Requires FF
-
Stream (fallback):
GET /log-service/streamwith log-service token- Token fetched via
GET /log-service/tokenwith PAT
- Token fetched via
Log viewer modes (FME flag vscode-log-experience):
inline— Logs in sidebar treeexpanded(default) — Logs open in editor tab with syntax highlighting (harness-log://URI scheme)
Host → Webview:
EXECUTION_UPDATE— Pipeline execution dataHISTORY_LIST— Paginated execution historyHISTORY_DETAIL— Full execution detail for history viewLOG_CHUNK— Step logs (lines array)GIT_CONTEXT— Git branch/SHA, org/project, FME variationsAPPROVAL_UPDATE— Harness native approvalEXTERNAL_APPROVAL_UPDATE— Jira/ServiceNow approvalSTEP_LOGS_OPENED_IN_TAB— Log opened in editor (not inline)RERUN_SUCCESS/RERUN_CANCELLED/RERUN_ERROR— Re-run outcome (success carriesnewPlanExecutionId)ABORT_SUCCESS/ABORT_CANCELLED/ABORT_ERROR— Abort outcomeSTO_SCAN— Security tab scan summary (parsed from execution graph)
Webview → Host:
approval— Approve/reject actionfetchStepLogs— Request logs for a specific stepfetchExecutionDetail— Load detail view for history executionfetchHistory— Request execution history pagesetDefaultView— Pin view preferencererunPipeline— Re-run a terminal execution (carriesplanExecutionId,pipelineIdentifier,firstStageId)abortPipeline— Abort a running execution (carriesplanExecutionId)
- Browse all pipelines in project
- Search, filter (All/Failed/Running/Waiting), sort, pin favorites
- Click any pipeline → view latest execution
- Works without git repository
- Paginated execution history (10-15 per page)
- Filter by status (All/Failed/Passed)
- Filter by pipeline
- Click execution → detail view with on-demand log fetching
- Works without git repository
- Full execution detail with stages/steps
- Live polling for running executions
- On-demand log fetching (click step to open logs in editor tab)
- Approval cards inline for Harness/Jira/ServiceNow approvals
- Re-run / Abort action button (status-adaptive)
The detail card has module-driven tabs, switched via state.activeDetailTab
(resets to pipeline on navigation). All parse data already in webview state —
no extra API calls.
- Pipeline — default stage/step tree.
- Build (
mi.ci) —parseBuild(): repo, branch (+ PR), commits, image/SBOM artifacts (pipelineCIInfo, withstepArtifactsfallback). - Deploy (
mi.cd) —parseDeploy(): per CD stage (layoutNodeMapmodule === 'cd') — services + manifests, environments, skip reasons. - Security (
mi.sto/ parsed scan) —parseStoScan()(host-side): per-severity tiles + new-vuln deltas; badge shows new critical+high.
Smart polling reduces API calls and battery usage:
- Pauses when sidebar hidden (
webviewView.visible === false) - Pauses when VS Code window loses focus (
vscode.window.state.focused === false) - Only polls when both sidebar visible AND window focused
- Auto-refreshes with fresh data when becoming visible/focused
Implementation: pipelinePoller.ts tracks visibility and focus via callbacks.
Authentication Methods:
-
PAT (Personal Access Token) — Traditional method
- Two-phase setup:
- Global (once):
Harness: Configure API Key→ Base URL, PAT (stored in SecretStorage), Account ID - Project (per workspace or global):
Harness: Select Org & Project→ Org/Project dropdowns
- Global (once):
- Settings:
harness.authSource = 'pat'
- Two-phase setup:
-
Environment Variables — Passwordless, CI/CD-friendly
- Set
HARNESS_API_KEY,HARNESS_BASE_URL,HARNESS_ACCOUNT_IDbefore launching VS Code - One-phase setup: Org/Project selection only (credentials read from env)
- Settings:
harness.authSource = 'env' - Resolution order: Environment variables → SecretStorage/Settings
- Set
First-run empty state:
- Auto-detects env vars on startup
- Panel A: Choose "Connect with environment variables" or "Connect with Personal Access Token"
- Panel D (env vars): Shows detected credentials, click "Connect" → Org/Project picker
- Panel E (PAT): Traditional 3-step flow (Base URL → PAT → Account ID → Org/Project)
- Auto-refreshes after completion (no reload needed)
Lifecycle Management:
Harness: Reset Auth Configuration— Clears all credentials + org/project settingsHarness: Select Org & Project— Change org/project globallyHarness: Switch Project (This Workspace)— Override org/project for current workspace only
Config resolution order: Environment variables → Workspace settings → Global settings
Workspace Safety:
- All workspace settings operations check if workspace is open first
- Prevents "Unable to write to Workspace Settings" error when no workspace is open
FME flag vscode-bar-experience:
simple— VS Code CSS variables, minimal stylingenhanced(default) — OKLCH color system, cards-based UI, light/dark auto-detection
Enhanced theme features:
- OKLCH color tokens for perceptual uniformity
- Single-focus rule: only first stage with interesting steps expanded
- Cards-based layout with elevated backgrounds
- Compact status icons (✓ × ⚠ ⏱)
- Detects
HarnessApprovalstep inexecutionGraph - Checks user permissions via
GET /ng/api/user-groups/{id}/member/{uuid} - Renders approval card inline with Approve/Reject buttons
- POST to
/gateway/pipeline/api/v1/orgs/{org}/projects/{project}/approvals/execution/{id}
- Detects
JiraApprovalorServiceNowApprovalstep - Extracts ticket info from
stepParameters.spec - Renders card with ticket link and approval/rejection criteria
- User updates ticket externally (no direct API call from extension)
A single action button on each execution card swaps based on status: terminal → Re-run, running → Abort.
- Fetch original inputSet YAML —
GET /pipeline/api/pipelines/execution/{planExecutionId}/inputsetV2(preserves runtime inputs) - Resolve the first stage's YAML identifier —
GET /pipeline/api/pipeline/execute/{planExecutionId}/retryStages(usesgroups[0].info[0].identifier, not the UUID) - Retry —
POST /pipeline/api/pipeline/execute/retry/{pipelineIdentifier}?planExecutionId=…&retryStages=<firstStage>&runAllStages=truewith the inputSet YAML as the body - New execution ID is read from
data.planExecution.uuid; extension registers it viapoller.setDetailExecution()and the webview navigates to the detail view
PUT /pipeline/api/pipeline/execute/interrupt/{planExecutionId}?interruptType=<type>- Confirmation dialog doubles as the interrupt-type picker: Abort All (
AbortAll) / Mark as Failed (UserMarkedFailure) - On success,
poller.refresh()picks up the terminal status, which swaps the button back to Re-run
- A re-run execution may 404 until queryable; the poller keeps active 1s polling within a waiting window instead of dropping to the heartbeat
tick()queues apendingRefreshif one is requested mid-tick (so a post-rerun/abortrefresh()is never lost)- A newly-created poller (after org/project switch) is seeded with current sidebar visibility, since visibility events only fire on change
Supports Claude Code (CLI/Extension), Cursor AI, and GitHub Copilot with automatic context injection via MCP.
- CLI mode: Fully automated (spawns subprocess, response in sidebar)
- Extension mode: Semi-automated (auto-opens panel, auto-pastes prompt)
- MCP config written to
~/.claude.json
- Auto-detected when running in Cursor editor (
vscode.env.appName.includes('cursor')) - Recommended: Install Harness Cursor Plugin — OAuth, zero config
- Fallback: Local MCP configuration (harness-mcp-v2)
- Auto-detected via VS Code extensions API (only in VS Code, not Cursor)
- Opens Copilot Chat and auto-pastes prompt
- Uses
"servers"key in MCP config (Copilot-specific format) - Environment variable inheritance: When using env var auth, only org/project IDs in config (credentials inherited from VS Code process)
MCP Configuration:
- Claude Code:
~/.claude.json(global) or<workspace>/.mcp.json(project) - Cursor:
- macOS/Linux:
~/.cursor/mcp.json - Windows:
%APPDATA%\Cursor\User\mcp.json
- macOS/Linux:
- GitHub Copilot:
- Project:
.vscode/mcp.json - Global (macOS):
~/Library/Application Support/Code/User/mcp.json - Global (Windows):
%APPDATA%\Code\User\mcp.json - Global (Linux):
~/.config/Code/User/mcp.json
- Project:
- Preserves existing config, only updates Harness MCP fields
- User must restart AI tool to activate
- Auth handling: When
authSource === 'env', writes environment variable references (${HARNESS_API_KEY}); whenauthSource === 'pat', writes actual credentials
Prompt Context:
- Pipeline name, status, execution ID
- Git branch and commit SHA
- Stage/step status with durations and error messages
- Harness execution URL
Tool preference persists across sessions via VS Code globalState.
A webview panel (separate from the sidebar), surfaced as "Harness AI Chat" (tab title, input placeholder, disclaimer), that streams from the Harness Intelligence chat API and renders full markdown + session history. The layout mirrors the web chat: greeting centered vertically, quick chips grouped near the input, minimal header (session title when active — see below).
Command: Harness: Open AI Chat (harness.openIntelligenceChat) — also
bound to ⌘⇧H (macOS) / Ctrl+⇧H (Windows/Linux). Opens or focuses the
panel and places the cursor in the input (FOCUS_INPUT webview message).
Endpoint (SSE):
POST /gateway/harness-intelligence/api/v2/chat?is_v2=false&orgIdentifier=…&projectIdentifier=…
body: { prompt, context:{currentUrl}, metadata, conversation:[], conversation_id?, system_event?, stream:true }
→ text/event-stream
The host reads the stream, forwards each event:/data: pair to the webview as
STREAM_EVENT, and handleSseEvent dispatches on the event name.
Request context: only currentUrl observed in captures — the backend
parses account/org/project/pipeline/stage from the Harness UI URL (same
URL-extraction pattern as the Harness MCP server).
Base URL: all Intelligence API calls use cfg.baseUrl from extension
config (harness.baseUrl / HARNESS_BASE_URL) — works for SaaS and
self-hosted instances that expose the same /gateway/harness-intelligence/…
routes.
Claude-style split input box (.ac-form):
- Top: auto-growing textarea (
.ac-form-body) - Divider: subtle neutral grey line (
--ac-input-divider) — not the outer animated border color - Footer: MCP connector pill (left) + circular send button (right)
Disclaimer under the input shows the focus shortcut (platform-aware).
- Active chat: header shows the backend-generated session title once
available (empty on new-chat greeting). Title sources: SSE
stream_metadata.title,session_title/titleevents, or a post-streamFETCH_SESSION_TITLEfallback (host lists sessions, webview getsSESSION_TITLE). - History view (overflow menu → History): full-panel list with search;
header reads History. Each session is a card (title + relative time,
e.g.
3h ago); ⋮ menu supports rename / copy conversation ID / delete. - Reopening a session sets the header title immediately; rename syncs header when the active session is renamed.
Shows which pipeline/execution the chat is scoped to and keeps it in sync as
the user navigates — an improvement over the web chat, which sends currentUrl
silently with no visible indicator.
- Auto-follow:
extension.tstrackscurrentViewedExecution; on identity change it callsupdateActiveChatContext()→ postsSET_CONTEXTto the open panel. Guarded bylastSyncedContextKeyso poll ticks don't spam it. - Chip:
renderContextChip()drawsContext: <pipeline> ×; it pulses (ac-context-flash) when context auto-updates on navigation. - Remove (×): sets
contextCleared, hides the chip, and stops sendingcurrentUrl(chat goes context-free). Navigating to a new pipeline re-attaches context and clears the flag.
External MCP connectors (Jira, GitHub, …) are enabled per-user-per-project via a backend setting — the chat request/body is identical whether or not they apply; the backend injects the tools from the stored setting keyed on the authenticated user + project. So a chat scoped to a project without connectors silently has no external tools.
- On open,
fetchSelectedConnectorIds()GETs/api/v1/user-settings/selected_connector_ids?orgIdentifier=…&projectIdentifier=…({ value: "id1,id2" }), and a status pill (ac-mcp-pill) in the input footer (below the divider) showsMCP · N connectors(green) orMCP · none, with a hover tooltip listing the connector names or explaining none are configured. - Verified via curl + browser captures: works over PAT (
x-api-key); the only variable is which project the extension points at.
Interactive cards emitted mid-stream to collect input. renderElicitation
draws them; on submit the webview posts a system_event
({ event_type, capability_id, result }) back through the same endpoint.
| SSE event | UI | result field |
|---|---|---|
elicitation_yaml / elicitation_confirm |
YAML/confirm card + action buttons | action_id (+ yaml, entity info) |
elicitation_free_text |
textarea | free_text |
elicitation_select |
option pills (single) | selection = chosen label |
elicitation_multi_select |
checkboxes (multi) | selections (array) + selection (comma-joined) |
elicitation_form |
per-field: select→dropdown, multi_select→checkboxes, text→textarea |
form_values keyed by field label; multi_select → array |
- Contracts verified against live web-app SSE + request captures (Chrome DevTools MCP) — field names are exact, not guessed.
- Cards lock on submit so a later edit can't re-enable Submit.
- History: stored answers live under
resolved.result;parseElicitationDataflattensresolved.resultintoresolvedso read-only replay highlights the previously-chosen options.
- Uses Split.io SDK for Harness FME integration
- Default embedded SDK key (works for all users)
- User targeting based on Harness user email
- Graceful degradation: FME failure → baseline behavior
Current flags:
vscode-log-experience— Log viewer mode (inline/expanded/drawer)vscode-bar-experience— Theme (simple/enhanced)vscode-mcp-integration— AI chat integration toggle- Default: ON (enabled by default for fail-safe behavior)
- 'on' or 'control': Enabled
- 'off': Disabled (only way to turn off AI bar)
Global Settings:
harness.baseUrl— Instance URL (default:https://app.harness.io)harness.accountIdentifier— Account IDharness.authSource— Authentication method (patorenv)harness.orgIdentifier— Organizationharness.projectIdentifier— Projectharness.pollingIntervalSeconds— Polling frequency (default: 10s)harness.defaultView— Default view (pipelinesorexecutions)harness.logLevel— Console verbosity (off/error/warn/info/debug)
Workspace Settings (optional overrides):
harness.orgIdentifierharness.projectIdentifier
Environment Variables (optional, passwordless auth):
HARNESS_API_KEY— Personal Access TokenHARNESS_BASE_URL— Instance URL (e.g.,https://app.harness.io)HARNESS_ACCOUNT_ID— Account identifier- Must be set before launching VS Code (inherited from parent process)
Configurable logger (src/utils/logger.ts) respects harness.logLevel setting.
Usage:
import { logger } from './utils/logger';
logger.debug('Component', 'Detailed trace', { data });
logger.info('Component', 'Operation started', context);
logger.warn('Component', 'Potential issue', details);
logger.error('Component', 'Operation failed:', error);Commands:
Harness: Open AI Chat— Open/focus Harness AI Chat panel (⌘⇧H / Ctrl+⇧H)Harness: Show Debug Output— View full API payloadsHarness: Debug FME Flags— View current feature flag statesHarness: Export Last Execution to JSON— Export execution data for debugging
- AIDA RCA — Endpoint not available (commented out in dispatcher)
- STO integration — Deferred to future release (commented out in dispatcher)
npm install
npm run compile # esbuild → dist/
# Run in VS Code: F5 → Extension Development Host
# Package: npm run package → harness-vscode-0.x.x.vsixFME SDK Key: Extension ships with default key. Override with HARNESS_FME_SDK_KEY env var or harness.fmeSdkKey setting for testing custom flags.