fix: unwrap workflow outputs in value_js + oauth2 in SSE server#564
fix: unwrap workflow outputs in value_js + oauth2 in SSE server#564
Conversation
…to SSE server Two bugs fixed: 1. Workflow output value_js received raw ReviewSummary wrappers instead of unwrapped step outputs. This caused every workflow tool (slack-search, slack-read-thread, discourse-read-thread, discourse-reply) to return "Unknown error" because outputs['step'].success was undefined (actual data was nested in .output). Script steps already unwrapped correctly via buildProviderTemplateContext, but workflow-executor's value_js, if conditions, and Liquid contexts did not. 2. executeHttpClientTool in mcp-custom-sse-server only handled bearer auth, not oauth2_client_credentials. When the AI called http_client tools with oauth2 auth (e.g. MongoDB Atlas), no token exchange happened and requests went out without Authorization headers, causing 401 errors. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pull Request OverviewSummaryThis PR primarily adds task telemetry visualization and local observability infrastructure improvements, along with several minor fixes across the codebase. Note: The PR description mentions fixes for workflow output unwrapping and OAuth2 in SSE server, but those specific files ( Files Changed AnalysisTask Telemetry & Trace Improvements (4 files, +900/-60 lines)
Observability Infrastructure (9 new files, +357 lines)
Other Improvements (15 files, +150/-30 lines)
Architecture & Impact AssessmentTask Telemetry SystemNew task summary extraction:
Trace report structure: interface TraceReport {
traceData: ResolvedTraceData;
tree: string; // YAML-formatted span tree
taskSummary: ProbeTaskSummary | null;
headerText: string; // Task summary header
text: string; // Combined header + tree
}Observability StackMulti-container architecture: graph TB
subgraph "Visor App"
A[OTel SDK] -->|OTLP| B[4318 HTTP / 4317 gRPC]
end
subgraph "Observability Stack"
B --> C[OTel Collector]
C -->|Traces| D[Tempo :3200]
C -->|Metrics| E[Prometheus :9091]
D -->|Service Map| F[Grafana :8001]
E -->|Metrics| F
G[Autoheal] -.->|Health Checks| C
G -.->|Health Checks| D
G -.->|Health Checks| E
G -.->|Health Checks| F
end
Benefits over LGTM:
Slack Live Updates Concurrency FixBefore: Race condition where After: private enqueue(text: string, mode: 'progress' | 'final') {
this.publishQueue = this.publishQueue
.catch(() => {})
.then(() => this.publish(text, mode));
return this.publishQueue;
}Scope Discovery & Context ExpansionDirect ImpactTask telemetry visualization:
Observability infrastructure:
Frontend error formatting:
Production ImpactSeverity: Low-Medium (improvements and observability enhancements) No critical bugs fixed in this PR - The description mentions workflow output unwrapping and OAuth2 fixes, but those files are not in the diff. ReferencesCode LocationsTask telemetry changes:
Observability stack:
Slack live updates:
Frontend error handling:
Related Patterns
Metadata
Powered by Visor from Probelabs Last updated: 2026-05-04T11:50:37.803Z | Triggered by: pr_updated | Commit: e6e082b 💡 TIP: You can chat with Visor using |
Security Issues (1)
Performance Issues (9)
Quality Issues (14)
Powered by Visor from Probelabs Last updated: 2026-05-04T11:50:07.635Z | Triggered by: pr_updated | Commit: e6e082b 💡 TIP: You can chat with Visor using |
- Fix on_message trigger dispatch to match normal message path: seed setFirstMessage so human_input checks auto-resolve and the full intent-router → build-config → generate-response chain runs with proper tool loading (Jira MCP, Slack, etc.) - Inject trigger.inputs.text as the AI message with original Slack message appended, so triggers can give specific instructions - Fix live update race condition: serialize publish() calls via a promise queue in SlackTaskLiveUpdateSink to prevent duplicate Slack messages when tick() and complete() run concurrently - Track inflightTick promise so complete()/fail() await in-flight ticks before publishing the final update - Fix self-bot message detection for bot_message subtypes by also checking ev.bot_id against the bot's own bot_id from auth.test - Add resolveChannelName() to SlackClient for #channel-name support in scheduler output targets via conversations.list with caching - Allow cron jobs without workflow (inputs.text as user message) - Make StaticCronJob.workflow optional in types - Fix workflow output warning to only fire for undefined (not null) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add debounce-manager for throttling check executions and integrate it into level-dispatch. Supports configurable throttle settings per check via config types. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add global uncaughtException handler that suppresses transient I/O errors (EIO, EPIPE, ECONNRESET, ERR_STREAM_DESTROYED) from dying child processes instead of crashing the entire visor process. Three layers of defense: - Global handler in child-process-error-handler.ts (imported early) - Worktree manager skips process.exit(1) for transient I/O errors - Stream-level error handlers on MCP transport stderr pipes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Update @probelabs/probe to v0.6.0-rc313 with enriched task telemetry (agent scope fields, full task state on events, task.items_json) - Parse task.items_json from batch events for proper titles on batch created/updated/completed/deleted operations - Collapse sub-agent scopes (engineer, code-explorer) that lack meaningful task titles into deduplicated single-line entries instead of showing repetitive generic "Engineer Task" items - Preserve sub-agent task titles when they exist (from task tool snapshots) - Group repeated sub-agent iterations under a single scope label Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
Workflow output unwrapping bug:
value_jsin workflow outputs received rawReviewSummarywrappers ({ issues: [], output: {...} }) instead of unwrapped step results. This caused all workflow tools (slack-search, slack-read-thread, discourse-read-thread, discourse-reply) to return{ success: false, error: "Unknown error" }becauseoutputs['step'].successwasundefined— the actual data was nested inside.output. AddedunwrapOutputs()helper (same logic asbuildProviderTemplateContext) and applied it tovalue_js,ifconditions, Liquid templates, and expression mappings.OAuth2 in SSE server:
executeHttpClientToolonly handledbearerauth, missingoauth2_client_credentials. When the AI called http_client tools with oauth2 auth (e.g. MongoDB Atlasatlas-api), no token exchange happened, causing 401 Unauthorized.Impact
This was causing complete tool failure in production. A traced task (e6a694b7) showed the AI calling slack-search 4x and slack-read-thread 9x over 7 minutes — every call returned "Unknown error", and the AI produced empty output
{"text":""}.Test plan
visor test --no-mocks --only discourse-read-real— passes, returns real thread contentvisor test --no-mocks --only atlas-list-projects-real— passes, oauth2 token exchange worksvisor test --only discourse-skill-activation— passes with mocks🤖 Generated with Claude Code