feat: Slack always uses durable execution mode with continuation stream#3006
feat: Slack always uses durable execution mode with continuation stream#3006
Conversation
|
|
The latest updates on your projects. Learn more about Vercel for GitHub.
1 Skipped Deployment
|
|
TL;DR — Forces all Slack agent interactions into durable execution mode so tool approvals suspend and resume correctly, then consumes the continuation SSE stream after approval to post the agent's follow-up response back into the Slack thread. Also adds delegated tool approval propagation — when a sub-agent needs approval during delegation, the request bubbles up through the parent agent's durable workflow. Key changes
Summary | 11 files | 4 commits | base: Durable execution mode forced for all Slack interactions
A single line in Continuation stream consumption after tool approval
Delegated sub-agent tool approval propagation
The flow spans several files:
OTel tracing restored with durable delegation features layered in
The latest commit (
Delegation tool results persisted in durable mode
The
|
b57eb67 to
73b4a0a
Compare
There was a problem hiding this comment.
Solid feature — the overall architecture of bubbling durable-approval-required artifacts up through delegation chains and consuming the continuation SSE stream from Slack is well thought-out. Two actionable issues below: one bug that will break Slack link rendering at runtime, and a console.info that should use the structured logger.
Claude Opus | 𝕏
packages/agents-work-apps/src/slack/services/events/block-actions.ts
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
PR Review Summary
(5) Total Issues | Risk: Medium
🟠⚠️ Major (2) 🟠⚠️
🟠 1) agentExecutionSteps.ts OTEL tracing removed from durable workflow steps
files: agentExecutionSteps.ts
Issue: The refactor removes all OpenTelemetry context propagation and span recording from callLlmStep and executeToolStep. Previously, these functions wrapped execution in otelContext.with(ctxWithBaggage, ...) to propagate conversation/tenant/project/agent IDs through spans, and recorded errors via setSpanWithError(span, rootCause). The imports for SPAN_NAMES, otelContext, propagation, setSpanWithError, and tracer are all removed.
Why: This eliminates observability for durable workflow execution paths:
- Span-level error recording is gone — failures won't appear in tracing dashboards
- Baggage propagation is removed — correlation IDs won't flow through the trace
- The
errorOpstreaming to clients is also removed, so clients may not see error states
If this is intentional (delegating tracing to the workflow engine infrastructure), it should be documented. If not, this is a significant observability regression.
Fix: Either:
- Restore OTEL tracing with baggage propagation and error span recording
- Document that the
'use step'workflow infrastructure provides equivalent tracing - Add a follow-up issue to re-instrument these paths once the durable workflow tracing story is clear
Refs:
- Removed OTEL imports
- Original otelContext.with() usage (on main branch)
🟠 2) agentExecutionSteps.ts Error handling removed — task status not updated to 'failed'
files: agentExecutionSteps.ts
Issue: The original code had explicit catch blocks in both callLlmStep and executeToolStep that:
- Wrote
errorOp()to the SSE stream so clients see the failure - Updated task status to
'failed'with error metadata in the database - Logged and recorded spans before re-throwing
The new code removes these error handlers entirely. Errors now propagate up without updating task status or notifying the stream.
Why: Failed executions won't be marked as 'failed' in the tasks table, and clients won't receive error events through the stream. The parent workflow's markWorkflowFailedStep only updates workflow_executions status, not the individual task status. This could leave tasks in an ambiguous state.
Fix: Restore the error handling that:
- Updates task status to
'failed'with error metadata - Streams
errorOp()to clients before the stream closes - Or confirm that the workflow-level error handler is sufficient and document the intentional behavior change
Refs:
Inline Comments:
- 🟠 Major:
agentExecutionSteps.ts:509-521Exception swallowed when pendingDurableApproval exists - 🟠 Major:
block-actions.ts:506-512Stream read errors leave users without feedback
🟡 Minor (3) 🟡
Inline Comments:
- 🟡 Minor:
agentExecution.ts:85-93Use structured logger instead of console.info - 🟡 Minor:
block-actions.ts:541escapeSlackMrkdwn destroys Slack links - 🟡 Minor:
block-actions.ts:497-504Empty catch block silently swallows JSON parse errors - 🟡 Minor:
block-actions.ts:476-479Timeout does not release reader lock after cancellation
💭 Consider (2) 💭
💭 1) tool-wrapper.ts:196-220 Helper functions defined inside hot path
Issue: findApprovalRequired and findApprovalInArtifacts are defined inline within the execute wrapper, creating new function instances on every tool execution.
Why: While JavaScript engines can optimize this, these pure functions could be moved to module scope for clarity. The nested search logic also duplicates structure knowledge that exists in A2A types.
Fix: Consider moving to module scope and importing A2A artifact type definitions to make the structure explicit rather than using Record<string, unknown> casts.
Refs: tool-wrapper.ts:196-220
💭 2) agent-types.ts:273-290 Type shape duplicated across 3 locations
Issue: The pendingDurableApproval and delegatedApproval type shapes are defined inline in:
agent-types.ts(AgentRunContext)Agent.ts(getPendingDurableApproval return type)agentExecutionSteps.ts(DelegatedApprovalContext)
Why: Changes to the shape require updates in multiple places. Consider extracting to a shared type definition.
Fix: Define DurableApprovalInfo and DelegatedApprovalContext once in agent-types.ts and import elsewhere.
Refs:
🕐 Pending Recommendations (1)
- 🟡
block-actions.ts:541escapeSlackMrkdwnbug already flagged by Pullfrog
🚫 REQUEST CHANGES
Summary: The core durable approval propagation logic looks well-designed — bubbling durable-approval-required artifacts through delegation chains and consuming continuation streams from Slack is architecturally sound. However, the removal of OTEL tracing and error handling from the workflow steps is a significant observability/reliability regression that should be addressed before merge. The Slack link rendering bug (escapeSlackMrkdwn) flagged by Pullfrog also needs fixing.
Discarded (8)
| Location | Issue | Reason Discarded |
|---|---|---|
generateTaskHandler.ts:56-89 |
buildDurableApprovalResult extraction | Valid DRY refactoring — used in 2 distinct locations, consolidates 20 lines |
agent-types.ts:270-290 |
Type extensions for delegated approval | Load-bearing types required for the feature |
tool-approval.ts:99-106 |
Removed toolApprovalUiBus publish for delegated agents | Intentional — durable mode handles approval UI via agentExecutionSteps |
agentExecutionSteps.ts:580-607 |
Delegated approval SSE streaming | Feature code, not a defect |
relationTools.ts:426-458 |
delegationMeta construction | Correct implementation of approval propagation |
streaming.ts:198 |
executionMode: 'durable' addition | Core feature — forces durable mode for Slack |
tool-wrapper.ts:112-116 |
skipHistoryStorage for durable delegation | Correct fix to prevent delegation loops |
agentExecution.ts:49 |
isPostApproval state tracking | Valid state management for continuation prompts |
Reviewers (6)
| Reviewer | Returned | Main Findings | Consider | While You're Here | Inline Comments | Pending Recs | Discarded |
|---|---|---|---|---|---|---|---|
pr-review-standards |
5 | 1 | 0 | 0 | 1 | 0 | 3 |
pr-review-errors |
6 | 0 | 0 | 0 | 3 | 0 | 3 |
pr-review-sre |
5 | 1 | 0 | 0 | 1 | 0 | 3 |
pr-review-types |
4 | 0 | 1 | 0 | 0 | 0 | 3 |
pr-review-consistency |
4 | 1 | 0 | 0 | 1 | 1 | 1 |
pr-review-precision |
8 | 0 | 1 | 0 | 0 | 0 | 7 |
| Total | 32 | 3 | 2 | 0 | 6 | 1 | 20 |
agents-api/src/domains/run/workflow/steps/agentExecutionSteps.ts
Outdated
Show resolved
Hide resolved
Preview URLsUse these stable preview aliases for testing this PR:
These point to the same Vercel preview deployment as the bot comment, but they stay stable and easier to find. Raw Vercel deployment URLs
|
There was a problem hiding this comment.
PR Review Summary
(0) New Issues | Risk: Low (delta-only review)
This is a delta review covering commit a010a0e66 — the only changes since the last automated review.
✅ Issues Addressed in This Commit
The commit successfully addresses 3 prior review findings:
| Prior Issue | Fix Applied |
|---|---|
🟡 escapeSlackMrkdwn destroys Slack links (Pullfrog, Claude) |
✅ Removed escapeSlackMrkdwn() call — now uses markdownToMrkdwn() only |
| 🟠 Stream read errors leave users without feedback (Claude) | ✅ Added error message posted to Slack when fullText is empty after stream failure |
| 🟡 Missing context in error log ([implicit]) | ✅ Added channel and threadTs to error log context |
🕐 Pending Recommendations (4)
Prior review findings that remain unaddressed (outside the scope of this delta commit):
- 🟠
agentExecutionSteps.tsOTEL tracing removed from durable workflow steps - 🟠
agentExecutionSteps.tsError handling removed — task status not updated to 'failed' - 🟡
agentExecution.ts:93Use structured logger instead ofconsole.info - 🟡
block-actions.ts:510-512Empty catch block silently swallows JSON parse errors (consider adding debug logging)
🚫 REQUEST CHANGES
Summary: The delta commit correctly fixes 3 prior review issues — good work! The escapeSlackMrkdwn bug is resolved and users now get feedback when stream consumption fails. However, the Major-severity items from the prior review (OTEL tracing removal, error handling removal in workflow steps) remain unaddressed. These observability/reliability concerns should be resolved before merge.
Reviewers (1)
| Reviewer | Returned | Main Findings | Consider | While You're Here | Inline Comments | Pending Recs | Discarded |
|---|---|---|---|---|---|---|---|
orchestrator (delta) |
0 | 0 | 0 | 0 | 0 | 4 | 0 |
| Total | 0 | 0 | 0 | 0 | 0 | 4 | 0 |
Note: Delta review — no sub-reviewers dispatched. Only analyzed the 1 file changed since last review.
2e6c785 to
6a7c806
Compare
9ead35b to
73b4a0a
Compare
There was a problem hiding this comment.
PR Review Summary
(1) Total Issues | Risk: Low
This is a delta review covering the latest commit 6a7c806b6 — "Restore OTEL tracing that was dropped during rebase conflict resolution".
✅ Prior Issues Addressed
Great work addressing the major concerns from previous reviews:
| Prior Issue | Status | Details |
|---|---|---|
| 🟠 OTEL tracing removed from durable workflow steps | ✅ Fixed | Commit 6a7c806b6 restores otelContext.with(), tracer.startActiveSpan, setSpanWithError, and baggage propagation |
| 🟠 Error handling removed — task status not updated | ✅ Fixed | Error handling in callLlmStep and executeToolStep restored with task status updates, errorOp streaming, and span error recording |
🟡 escapeSlackMrkdwn destroys Slack links |
✅ Fixed | Commit a010a0e66 removed the redundant escapeSlackMrkdwn() call |
| 🟠 Stream errors leave users without feedback | ✅ Fixed | Commit a010a0e66 adds user-facing error message when fullText is empty after stream failure |
🟡 Minor (1) 🟡
Inline Comments:
- 🟡 Minor:
agentExecution.ts:85-93Use structured logger instead ofconsole.info
💭 Consider (1) 💭
💭 1) block-actions.ts:510-512 Empty catch block for JSON parse errors
Issue: Invalid JSON lines in the SSE stream are silently ignored with just a // skip invalid JSON comment.
Why: While this may be intentional (skip malformed lines without disrupting the stream), there's no visibility into how often this happens or what data is being dropped.
Fix: Consider adding debug-level logging to preserve visibility:
catch (parseError) {
logger.debug({ jsonStr, error: parseError }, 'Skipping invalid JSON in SSE stream');
}Refs:
- api-logging-guidelines skill — debug level for expected failures
📝 Implementation Note
The inline comment suggestion for console.info → logger.info requires adding an import:
import { getLogger } from '../../../../logger';
const logger = getLogger('agentExecution');💡 APPROVE WITH SUGGESTIONS
Summary: The latest commit successfully restores the OTEL tracing and error handling that was flagged in prior reviews — nice work! 🎉 The core feature implementation (durable execution mode for Slack, delegated approval propagation, continuation stream consumption) is architecturally sound. Only minor logging consistency issues remain. Ship it!
Discarded (0)
All prior major findings have been addressed. No new findings discarded in this delta review.
Reviewers (1)
| Reviewer | Returned | Main Findings | Consider | While You're Here | Inline Comments | Pending Recs | Discarded |
|---|---|---|---|---|---|---|---|
orchestrator (delta) |
2 | 0 | 1 | 0 | 1 | 0 | 0 |
| Total | 2 | 0 | 1 | 0 | 1 | 0 | 0 |
Note: Delta review — no sub-reviewers dispatched. Focused on validating fixes for prior review findings and the new OTEL restoration commit.
There was a problem hiding this comment.
PR Review Summary
(6) Total Issues | Risk: High
⚠️ Force-push detected: The PR branch was force-pushed after previous reviews. Commits6a7c806b6anda010a0e66(which contained fixes for OTEL tracing, error handling, and theescapeSlackMrkdwnbug) are no longer present. This review reflects the current state of the code.
🔴❗ Critical (0) ❗🔴
None.
🟠⚠️ Major (4) 🟠⚠️
🟠 1) agentExecutionSteps.ts OTEL tracing removed from durable workflow steps
files: agentExecutionSteps.ts
Issue: OpenTelemetry context propagation and span recording were removed from callLlmStep and executeToolStep. The file previously imported SPAN_NAMES, otelContext, propagation, setSpanWithError, and tracer, wrapped execution in otelContext.with(ctxWithBaggage, ...) with baggage for conversation.id, tenant.id, project.id, and agent.id. All of this is now gone.
Why: This eliminates observability for durable workflow execution paths:
- Span-level error recording via
setSpanWithErroris removed — failures won't appear in tracing dashboards with proper error status - Baggage propagation is removed — trace correlation IDs won't flow through execution
- Debugging production incidents in durable mode becomes significantly harder
Fix: Restore OTEL instrumentation:
- Re-add imports:
SPAN_NAMES,otelContext,propagation,setSpanWithError,tracer - Wrap try block in
callLlmStepwithotelContext.with(ctxWithBaggage, ...)using baggage entries - Add
tracer.startActiveSpan(SPAN_NAMES.EXECUTION_HANDLER_EXECUTE, ...)around completion/error handling - Call
setSpanWithError(span, error)in catch blocks - Same pattern for
executeToolStepwithSPAN_NAMES.DURABLE_TOOL_EXECUTION
Refs:
🟠 2) agentExecutionSteps.ts Error handling removed — task status not updated to 'failed'
files: agentExecutionSteps.ts
Issue: The original callLlmStep and executeToolStep had catch blocks that: (1) updated task status to 'failed' with error metadata, (2) streamed errorOp to clients so they see the failure, (3) recorded errors on spans. The current implementation has no catch blocks — errors propagate directly to the workflow wrapper.
Why: Without error handling:
- Task records remain in 'pending' or 'running' state when they should be 'failed'
- Clients don't receive
errorOpevents — they may hang waiting for a response - The workflow-level catch only calls
markWorkflowFailedStep, which doesn't update task records or notify clients
Fix: Add try-catch blocks that:
- Import and call
errorOpto stream error viasseHelper.writeOperation(errorOp(...)) - Call
updateTaskto setstatus: 'failed'with error metadata - Re-throw the error so the workflow wrapper can also handle it
Refs:
Inline Comments:
- 🟠 Major:
block-actions.ts:532escapeSlackMrkdwndestroys Slack links - 🟠 Major:
block-actions.ts:531-546No user feedback when stream fails
🟡 Minor (1) 🟡
Inline Comments:
- 🟡 Minor:
agentExecution.ts:85-93Use structured logger instead ofconsole.info
💭 Consider (1) 💭
Inline Comments:
- 💭 Consider:
block-actions.ts:511-513Empty catch block silently swallows JSON parse errors
🚫 REQUEST CHANGES
Summary: The core feature implementation (durable execution mode for Slack, delegated approval propagation through A2A artifacts, continuation stream consumption) is architecturally sound. However, the PR was force-pushed and lost critical fixes that were previously reviewed and approved:
- OTEL tracing removal — eliminates observability for durable workflow paths
- Error handling removal — tasks won't be marked as failed, clients won't receive error notifications
escapeSlackMrkdwnbug — destroys Slack links in approval continuation responses- Missing stream error feedback — users get no notification when continuation fails
Please restore the commits that contained the OTEL and error handling fixes (6a7c806b6), and apply the Slack bug fixes from a010a0e66.
Discarded (1)
| Location | Issue | Reason Discarded |
|---|---|---|
block-actions.ts:208-209 |
Missing Zod validation for InlineSelectorMetadata | Pre-existing issue, not introduced by this PR |
Reviewers (5)
| Reviewer | Returned | Main Findings | Consider | While You're Here | Inline Comments | Pending Recs | Discarded |
|---|---|---|---|---|---|---|---|
pr-review-standards |
2 | 0 | 0 | 0 | 2 | 0 | 0 |
pr-review-errors |
2 | 0 | 0 | 0 | 2 | 0 | 0 |
pr-review-sre |
5 | 2 | 1 | 0 | 2 | 0 | 0 |
pr-review-consistency |
0 | 0 | 0 | 0 | 0 | 0 | 0 |
pr-review-appsec |
4 | 0 | 0 | 0 | 3 | 0 | 1 |
| Total | 13 | 2 | 1 | 0 | 4 | 0 | 1 |
Note: Several findings were deduplicated across reviewers (OTEL removal, escapeSlackMrkdwn bug, etc.)
packages/agents-work-apps/src/slack/services/events/block-actions.ts
Outdated
Show resolved
Hide resolved
73b4a0a to
15a4d6c
Compare
There was a problem hiding this comment.
PR Review Summary
(6) Total Issues | Risk: High
🟠⚠️ Major (4) 🟠⚠️
🟠 1) agentExecutionSteps.ts OTEL tracing removed from durable workflow steps
files: agentExecutionSteps.ts
Issue: OpenTelemetry context propagation and span recording were removed from callLlmStep and executeToolStep. The file previously imported SPAN_NAMES, otelContext, propagation, setSpanWithError, and tracer, wrapped execution in otelContext.with(ctxWithBaggage, ...) with baggage for conversation.id, tenant.id, project.id, and agent.id. All of this instrumentation is now gone.
Why: This eliminates observability for durable workflow execution paths:
- Span-level error recording via
setSpanWithErroris removed — failures won't appear in tracing dashboards with proper error status - Baggage propagation is removed — trace correlation IDs won't flow through execution
- Debugging production incidents in durable mode becomes significantly harder
Fix: Restore OTEL instrumentation:
- Re-add imports:
SPAN_NAMES,otelContext,propagation,setSpanWithError,tracer - Wrap try block in
callLlmStepwithotelContext.with(ctxWithBaggage, ...)using baggage entries - Add
tracer.startActiveSpan(SPAN_NAMES.EXECUTION_HANDLER_EXECUTE, ...)around completion/error handling - Call
setSpanWithError(span, error)in catch blocks - Same pattern for
executeToolStepwithSPAN_NAMES.DURABLE_TOOL_EXECUTION
Refs:
🟠 2) agentExecutionSteps.ts Error handling removed — task status not updated to 'failed'
files: agentExecutionSteps.ts
Issue: The callLlmStep function (lines 507-743) has a try-finally block with NO catch block. The original code had explicit catch blocks that: (1) updated task status to 'failed' with error metadata, (2) streamed errorOp to clients so they see the failure, (3) recorded errors on spans. Errors now propagate directly to the workflow wrapper without any of this handling.
Why: Without error handling:
- Task records remain in 'pending' or 'running' state when they should be 'failed'
- Clients don't receive
errorOpevents — they may hang waiting for a response - The workflow-level catch only calls
markWorkflowFailedStep, which doesn't update task records or notify clients
Fix: Add try-catch blocks that:
- Import and call
errorOpto stream error viasseHelper.writeOperation(errorOp(...)) - Call
updateTaskto setstatus: 'failed'with error metadata - Re-throw the error so the workflow wrapper can also handle it
Refs:
Inline Comments:
- 🟠 Major:
block-actions.ts:532escapeSlackMrkdwndestroys Slack links - 🟠 Major:
block-actions.ts:531-546No user feedback when stream fails
🟡 Minor (1) 🟡
Inline Comments:
- 🟡 Minor:
agentExecution.ts:87-95Use structured logger instead ofconsole.info
💭 Consider (1) 💭
Inline Comments:
- 💭 Consider:
block-actions.ts:506-513Empty catch block silently swallows JSON parse errors
🕐 Pending Recommendations (5)
Prior review findings that remain unaddressed:
- 🟠
agentExecutionSteps.tsOTEL tracing removed from durable workflow steps (from earlier Claude review) - 🟠
agentExecutionSteps.tsError handling removed — task status not updated to 'failed' (from earlier Claude review) - 🟡
block-actions.ts:532escapeSlackMrkdwnbug (from Pullfrog review) - 🟡
agentExecution.ts:93Use structured logger instead of console.info (from Pullfrog review) - 🟡
agentExecution.ts:51Consider aligningisPostApprovalandapprovalRoundstate (from Pullfrog review)
🚫 REQUEST CHANGES
Summary: The core feature implementation (durable execution mode for Slack, delegated approval propagation through A2A artifacts, continuation stream consumption) is architecturally sound. However, there are significant regressions that need to be addressed before merge:
-
OTEL tracing removal — eliminates observability for durable workflow paths. This appears to have been lost during a rebase or refactor. The prior reviews noted commits existed that fixed this but were force-pushed away.
-
Error handling removal — tasks won't be marked as failed, clients won't receive error notifications. The workflow-level catch only updates
workflow_executionsstatus, not individual task status. -
Slack link rendering bug —
escapeSlackMrkdwn(markdownToMrkdwn(...))double-escapes angle brackets, breaking links. -
Missing stream error feedback — users get no notification when continuation stream fails.
Please restore the OTEL tracing and error handling from the original implementation, and apply the Slack bug fixes.
Discarded (3)
| Location | Issue | Reason Discarded |
|---|---|---|
generateTaskHandler.ts:56-89 |
buildDurableApprovalResult extraction |
Valid DRY refactoring — used in 2 distinct locations, consolidates code |
tool-wrapper.ts:196-220 |
Helper functions defined inside hot path | Minor optimization concern — JavaScript engines optimize this well |
agentExecution.ts:49-51 |
isPostApproval and approvalRound track same concept differently |
Developer preference — both work correctly |
Reviewers (4)
| Reviewer | Returned | Main Findings | Consider | While You're Here | Inline Comments | Pending Recs | Discarded |
|---|---|---|---|---|---|---|---|
pr-review-standards |
3 | 2 | 0 | 0 | 1 | 0 | 0 |
pr-review-errors |
4 | 0 | 0 | 0 | 2 | 0 | 2 |
pr-review-sre |
6 | 2 | 0 | 0 | 2 | 0 | 2 |
pr-review-consistency |
2 | 0 | 0 | 0 | 2 | 0 | 0 |
| Total | 15 | 4 | 0 | 0 | 4 | 0 | 4 |
Note: Several findings were deduplicated across reviewers (OTEL removal, escapeSlackMrkdwn bug flagged by multiple reviewers).
packages/agents-work-apps/src/slack/services/events/block-actions.ts
Outdated
Show resolved
Hide resolved
When a delegated sub-agent requires tool approval in durable mode, the approval now flows through the workflow hook system instead of the in-memory pub/sub bus. The parent agent detects `durable-approval-required` artifacts from sub-agents, surfaces them as SSE approval events, and forwards the user's decision back via `delegatedToolApproval` context on re-execution. Adds `isPostApproval` flag to prevent the workflow loop from re-sending the original user message after approval. Includes validation of approval artifact fields, SSE error handling for delegated approval streaming, logging for suppressed errors in the durable approval catch path, and type-safe metadata construction for delegation.
- Add executionMode: 'durable' to all Slack chat requests, overriding per-agent config - After tool approval in block-actions handler, consume the durable continuation SSE stream and post the result back to the Slack thread - Convert standard markdown to Slack mrkdwn format in continuation responses - Improve logging: use warn level for expected durable approval errors, pass full error objects for better diagnostics
…or feedback for stream failures
1c50dc1 to
c637f86
Compare
There was a problem hiding this comment.
PR Review Summary
(0) New Issues | Risk: Low (delta-only review)
This is a delta review covering commit 1c50dc17a — "Address review: remove escapeSlackMrkdwn that destroys links, add error feedback for stream failures".
✅ Prior Issues Addressed in This Commit
The commit successfully addresses 3 prior review findings:
| Prior Issue | Fix Applied |
|---|---|
🟡 block-actions.ts:540 escapeSlackMrkdwn destroys Slack links (Pullfrog, Claude) |
✅ Removed escapeSlackMrkdwn() call — now uses markdownToMrkdwn() only |
🟠 block-actions.ts:531-546 Stream errors leave users without feedback |
✅ Added error message posted to Slack when fullText is empty after stream failure (lines 520-528) |
| 🟡 Missing context in error log | ✅ Added channel and threadTs to error log context (line 517) |
🕐 Pending Recommendations (4)
Prior review findings that remain unaddressed (outside the scope of this delta commit):
- 🟠
agentExecutionSteps.tsOTEL tracing removed from durable workflow steps — span recording, baggage propagation, andsetSpanWithErrorcalls removed - 🟠
agentExecutionSteps.tsError handling removed — task status not updated to 'failed', clients don't receiveerrorOpevents - 🟡
agentExecution.ts:87-95Use structured logger instead ofconsole.info(JSON.stringify({...})) - 💭
block-actions.ts:510-512Empty catch block silently swallows JSON parse errors (consider adding debug logging)
🚫 REQUEST CHANGES
Summary: The delta commit correctly fixes 3 prior review issues — nice work! 🎉 The escapeSlackMrkdwn bug is resolved and users now get feedback when stream consumption fails. The error logging context improvement is a nice touch.
However, the Major-severity items from prior reviews remain unaddressed:
- OTEL tracing removal in
agentExecutionSteps.ts— this eliminates observability for durable workflow execution paths - Error handling removal in
agentExecutionSteps.ts— tasks won't be marked as 'failed' and clients won't receive error notifications
These observability/reliability concerns should be resolved before merge.
Discarded (0)
No new findings discarded — this delta commit introduced no issues.
Reviewers (1)
| Reviewer | Returned | Main Findings | Consider | While You're Here | Inline Comments | Pending Recs | Discarded |
|---|---|---|---|---|---|---|---|
orchestrator (delta) |
0 | 0 | 0 | 0 | 0 | 4 | 0 |
| Total | 0 | 0 | 0 | 0 | 0 | 4 | 0 |
Note: Delta review — no sub-reviewers dispatched. Focused on validating fixes for prior review findings in the 1 file changed since last review.
There was a problem hiding this comment.
PR Review Summary
(5) Total Issues | Risk: High
🟠⚠️ Major (2) 🟠⚠️
🟠 1) agentExecutionSteps.ts OTEL tracing removed from durable workflow steps
files: agentExecutionSteps.ts
Issue: OpenTelemetry context propagation and span recording were completely removed from callLlmStep and executeToolStep. The main branch imports SPAN_NAMES, otelContext, propagation, setSpanWithError, and tracer, wraps execution in otelContext.with(ctxWithBaggage, ...) with baggage for conversation.id, tenant.id, project.id, and agent.id, and records errors via setSpanWithError(span, rootCause). All of this instrumentation is now gone.
Why: This eliminates observability for durable workflow execution paths:
- Span-level error recording via
setSpanWithErroris removed — failures won't appear in tracing dashboards with proper error status - Baggage propagation is removed — trace correlation IDs (conversation.id, tenant.id, etc.) won't flow through execution
- The spans for
SPAN_NAMES.EXECUTION_HANDLER_EXECUTEandSPAN_NAMES.DURABLE_TOOL_EXECUTIONare no longer created - Debugging production incidents in durable mode becomes significantly harder
Fix: Restore OTEL instrumentation:
- Re-add imports:
SPAN_NAMES,otelContext,propagation,setSpanWithError,tracer - Wrap try block in
callLlmStepwithotelContext.with(ctxWithBaggage, ...)using baggage entries - Add
tracer.startActiveSpan(SPAN_NAMES.EXECUTION_HANDLER_EXECUTE, ...)around completion/error handling - Call
setSpanWithError(span, error)in catch blocks - Same pattern for
executeToolStepwithSPAN_NAMES.DURABLE_TOOL_EXECUTION
Refs:
🟠 2) agentExecutionSteps.ts Error handling removed — task status not updated to 'failed'
files: agentExecutionSteps.ts
Issue: The callLlmStep function (lines 507-755) has a try-finally block with NO catch block. The main branch has explicit catch blocks that: (1) update task status to 'failed' with error metadata via updateTask(), (2) stream errorOp to clients so they see the failure, (3) record errors on OTEL spans. Errors now propagate directly to the workflow wrapper without any of this handling.
Why: Without error handling:
- Task records remain in 'pending' or 'running' state when they should be 'failed'
- Clients don't receive
errorOpevents — they may hang waiting for a response that never comes - The workflow-level catch only calls
markWorkflowFailedStep, which updatesworkflow_executionsstatus but doesn't update individual task status or notify clients
Fix: Add catch blocks that:
- Import and call
errorOpto stream error viasseHelper.writeOperation(errorOp(...)) - Call
updateTaskto setstatus: 'failed'with error metadata - Re-throw the error so the workflow wrapper can also handle it
Refs:
Inline Comments:
- 🟠 Major:
block-actions.ts:539-554Empty success case leaves users with no feedback
🟡 Minor (2) 🟡
🟡 1) agent-types.ts + agentExecutionSteps.ts + Agent.ts Type duplication across 3 locations
files: agent-types.ts, agentExecutionSteps.ts, Agent.ts
Issue: The DelegatedApprovalContext type shape ({ toolCallId, toolName, args, subAgentId }) is defined in three places:
agentExecutionSteps.tslines 34-39 asDelegatedApprovalContextagent-types.tslines 277-283 inline withinAgentRunContext.pendingDurableApproval.delegatedApprovalAgent.tslines 166-171 inline ingetPendingDurableApproval()return type
Why: Having three parallel definitions of the same shape creates maintenance burden and risks drift. The codebase convention for shared types is to define them once in agent-types.ts.
Fix: Define DelegatedApprovalContext once in agent-types.ts and export it, then import and reference it in the other locations.
Refs:
Inline Comments:
- 🟡 Minor:
agentExecution.ts:87-95Use structured logger instead ofconsole.info
💭 Consider (1) 💭
💭 1) block-actions.ts:510-512 Empty catch block for JSON parsing follows existing convention
Issue: The JSON parsing in consumeApprovalContinuationStream has an empty catch block with only a // skip invalid JSON comment.
Why: This actually follows the established convention in streaming.ts (line 554), which uses the same pattern. However, adding debug-level logging would improve visibility into parsing issues without breaking the pattern.
Fix: Optionally add debug logging: catch (parseError) { logger.debug({ jsonStr: jsonStr.slice(0, 100) }, 'Skipping invalid JSON in SSE stream'); }
Refs:
🕐 Pending Recommendations (5)
Prior review findings that remain unaddressed:
- 🟠
agentExecutionSteps.tsOTEL tracing removed from durable workflow steps (from earlier Claude review) - 🟠
agentExecutionSteps.tsError handling removed — task status not updated to 'failed' (from earlier Claude review) - 🟡
agentExecution.ts:93Use structured logger instead of console.info (from Pullfrog review) - 🟡
agentExecution.ts:51Consider aligningisPostApprovalandapprovalRoundstate (from Pullfrog review) - 💭
block-actions.ts:510-512Empty catch block silently swallows JSON parse errors (from earlier Claude review)
🚫 REQUEST CHANGES
Summary: The core feature implementation is architecturally sound — forcing durable execution mode for Slack, propagating delegated approvals through A2A artifacts, and consuming continuation streams are well-designed. The latest commit (c637f868) correctly fixed the escapeSlackMrkdwn bug and added error feedback for stream failures.
However, two Major-severity regressions remain unaddressed:
- OTEL tracing removal — eliminates observability for durable workflow paths, making production debugging significantly harder
- Error handling removal — tasks won't be marked as 'failed' and clients won't receive error notifications
These appear to have been lost during a rebase or refactor rather than intentionally removed. Please restore the OTEL instrumentation and error handling from the main branch.
Discarded (3)
| Location | Issue | Reason Discarded |
|---|---|---|
generateTaskHandler.ts:56-89 |
buildDurableApprovalResult extraction |
Valid DRY refactoring — consolidates duplicate artifact construction code |
tool-wrapper.ts:196-220 |
Helper functions defined inside hot path | Follows JavaScript patterns, functions need no external state, negligible perf impact |
streaming.ts:198 |
executionMode: 'durable' addition |
Core feature — intentional forcing of durable mode for Slack |
Reviewers (4)
| Reviewer | Returned | Main Findings | Consider | While You're Here | Inline Comments | Pending Recs | Discarded |
|---|---|---|---|---|---|---|---|
pr-review-standards |
0 | 0 | 0 | 0 | 0 | 0 | 0 |
pr-review-errors |
1 | 0 | 0 | 0 | 1 | 0 | 0 |
pr-review-sre |
0 | 0 | 0 | 0 | 0 | 0 | 0 |
pr-review-consistency |
4 | 1 | 1 | 0 | 1 | 0 | 1 |
orchestrator |
2 | 2 | 0 | 0 | 0 | 5 | 2 |
| Total | 7 | 3 | 1 | 0 | 2 | 5 | 3 |
Note: Most findings were deduplicated against prior reviews to avoid repetition. The OTEL and error handling issues were already raised in previous reviews and are tracked as Pending Recommendations.
| console.info( | ||
| JSON.stringify({ | ||
| msg: '[agentExecution] Creating tool approval hook', | ||
| hookToolCallId, | ||
| parentToolCallId: toolCall.toolCallId, | ||
| isDelegated: !!llmResult.delegatedApproval, | ||
| workflowRunId, | ||
| }) | ||
| ); |
There was a problem hiding this comment.
🟡 Minor: Use structured logger instead of console.info
Issue: Using console.info(JSON.stringify({...})) bypasses the structured logging infrastructure used consistently throughout the workflow code.
Why: The sibling file agentExecutionSteps.ts uses getLogger('agentExecutionSteps') for consistent log formatting, redaction support, and correlation IDs. Using console.info creates inconsistent log output that won't appear in log aggregation with proper metadata.
Fix: Add import and logger at module scope, then use structured logging:
import { getLogger } from '../../../../logger';
const logger = getLogger('agentExecution');
// Then replace console.info with:
logger.info(
{
hookToolCallId,
parentToolCallId: toolCall.toolCallId,
isDelegated: !!llmResult.delegatedApproval,
workflowRunId,
},
'[agentExecution] Creating tool approval hook'
);Refs:
| if (fullText.length > 0) { | ||
| const slackText = markdownToMrkdwn(fullText); | ||
| await slackClient.chat | ||
| .postMessage({ | ||
| channel, | ||
| ...threadParam, | ||
| text: slackText, | ||
| blocks: [ | ||
| { type: 'section', text: { type: 'mrkdwn', text: slackText } }, | ||
| createContextBlock({ agentName }), | ||
| ], | ||
| }) | ||
| .catch((e) => | ||
| logger.warn({ error: e, channel, threadTs }, 'Failed to post approval continuation result') | ||
| ); | ||
| } |
There was a problem hiding this comment.
🟠 MAJOR: No user feedback when stream completes with empty text
Issue: When consumeApprovalContinuationStream completes successfully but fullText is empty (e.g., stream emits only non-text-delta events, or uses an unexpected format), the user receives no feedback. The 'thinking...' message is deleted but no follow-up is posted.
Why: After approving a tool, users see "agentName is thinking..." appear then disappear with no explanation. Unlike the error case (lines 520-528) which posts "Something went wrong...", the empty-success case leaves users unable to determine if the operation completed successfully.
Fix: Add an else branch to inform users when no text was produced:
| if (fullText.length > 0) { | |
| const slackText = markdownToMrkdwn(fullText); | |
| await slackClient.chat | |
| .postMessage({ | |
| channel, | |
| ...threadParam, | |
| text: slackText, | |
| blocks: [ | |
| { type: 'section', text: { type: 'mrkdwn', text: slackText } }, | |
| createContextBlock({ agentName }), | |
| ], | |
| }) | |
| .catch((e) => | |
| logger.warn({ error: e, channel, threadTs }, 'Failed to post approval continuation result') | |
| ); | |
| } | |
| if (fullText.length > 0) { | |
| const slackText = markdownToMrkdwn(fullText); | |
| await slackClient.chat | |
| .postMessage({ | |
| channel, | |
| ...threadParam, | |
| text: slackText, | |
| blocks: [ | |
| { type: 'section', text: { type: 'mrkdwn', text: slackText } }, | |
| createContextBlock({ agentName }), | |
| ], | |
| }) | |
| .catch((e) => | |
| logger.warn({ error: e, channel, threadTs }, 'Failed to post approval continuation result') | |
| ); | |
| } else { | |
| logger.warn({ channel, threadTs, agentName }, 'Approval continuation stream produced empty response'); | |
| await slackClient.chat | |
| .postMessage({ | |
| channel, | |
| ...threadParam, | |
| text: `_${agentName} completed the tool execution but produced no visible response._`, | |
| }) | |
| .catch((e) => | |
| logger.warn({ error: e, channel, threadTs }, 'Failed to post empty response notification') | |
| ); | |
| } | |
| } |
Refs:
- pr-review-errors guidance — error feedback should inform users
Reset to main's version (with full OTEL tracing wrappers and error handling) and re-applied durable delegation features on top, inside the existing otelContext.with and tracer.startActiveSpan wrappers.
There was a problem hiding this comment.
PR Review Summary
(1) Total Issues | Risk: Low
This is a delta review covering commit ecd719cc4 — "Restore OTEL tracing and error handling in agentExecutionSteps".
✅ Prior Major Issues Addressed
Great work addressing the critical concerns from previous reviews! 🎉
| Prior Issue | Status | Details |
|---|---|---|
| 🟠 OTEL tracing removed from durable workflow steps | ✅ Fixed | Commit restores otelContext.with(), tracer.startActiveSpan, setSpanWithError, and baggage propagation for conversation.id, tenant.id, project.id, agent.id |
| 🟠 Error handling removed — task status not updated | ✅ Fixed | Catch blocks restored in both callLlmStep and executeToolStep with task status updates to 'failed', errorOp streaming to clients, and span error recording |
🟡 escapeSlackMrkdwn destroys Slack links |
✅ Fixed | Commit c637f868 removed the redundant escapeSlackMrkdwn() call |
| 🟠 Stream errors leave users without feedback | ✅ Fixed | Error message posted to Slack when fullText is empty after stream failure |
🟡 Minor (1) 🟡
Inline Comments:
- 🟡 Minor:
agentExecution.ts:87-95Use structured logger instead ofconsole.info
💭 Consider (1) 💭
💭 1) block-actions.ts:510-512 Empty catch block for JSON parse errors
Issue: Invalid JSON lines in the SSE stream are silently ignored with just a // skip invalid JSON comment.
Why: While skipping malformed JSON is often correct for SSE streams (and follows the pattern in streaming.ts), there's no visibility into how often this happens. During incidents, this makes it harder to diagnose parsing failures.
Fix: Consider adding debug-level logging:
catch (parseError) {
logger.debug({ jsonStr: jsonStr.slice(0, 100) }, 'Skipping invalid JSON in SSE stream');
}Refs:
- api-logging-guidelines skill — debug level for expected failures
💡 APPROVE WITH SUGGESTIONS
Summary: The latest commit successfully restores the OTEL tracing and error handling that was the main blocker from prior reviews — excellent work! 🎉 The core feature implementation (durable execution mode for Slack, delegated approval propagation through A2A artifacts, continuation stream consumption) is architecturally sound. Only one minor logging consistency issue remains. Ship it!
Discarded (0)
All prior major findings have been addressed. No new findings discarded in this delta review.
Reviewers (1)
| Reviewer | Returned | Main Findings | Consider | While You're Here | Inline Comments | Pending Recs | Discarded |
|---|---|---|---|---|---|---|---|
orchestrator (delta) |
2 | 0 | 1 | 0 | 1 | 0 | 0 |
| Total | 2 | 0 | 1 | 0 | 1 | 0 | 0 |
Note: Delta review — no sub-reviewers dispatched. Focused on validating fixes for prior review findings and the OTEL restoration commit.
Ito Test Report ❌12 test cases ran. 1 failed, 2 additional findings, 9 passed. Overall, 12 test cases were executed with 9 passing and 3 failing, confirming that Slack approval continuation flows, delegated approval context propagation, key edge cases (non-SSE gating, malformed SSE tolerance, timeout cleanup, and duplicate-approve idempotency), and core security checks (ownership enforcement, invalid signature rejection, and stale-timestamp replay rejection) behaved as expected. The most important findings were three real defects: a high-severity approval payload integrity gap that allows tampered identifiers to retarget approvals across contexts, a medium-severity mrkdwn sanitization issue that permits mention/formatting injection in continuation messages, and a high-severity approve/deny race condition that can show contradictory terminal Slack outcomes for the same approval. ❌ Failed (1)
🟠 Malicious markdown payload is sanitized in continuation Slack message
Relevant code:
if (fullText.length > 0) {
const slackText = markdownToMrkdwn(fullText);
await slackClient.chat.postMessage({
channel,
...threadParam,
text: slackText,
blocks: [
{ type: 'section', text: { type: 'mrkdwn', text: slackText } },
createContextBlock({ agentName }),
],
});
}
export function markdownToMrkdwn(markdown: string): string {
if (!markdown) return markdown;
let result = markdown;
result = result.replace(/^#{1,6}\s+(.+)$/gm, '*$1*');
result = result.replace(
/\[([^\]]+)\]\(([^)]+)\)/g,
(_, text, url) => `<${url}|${escapeSlackLinkText(text)}>`
);
result = result.replace(/\*\*([^*]+)\*\*/g, '*$1*');
result = result.replace(/__([^_]+)__/g, '*$1*');
result = result.replace(/~~([^~]+)~~/g, '~$1~');
return result;
}✅ Passed (9)ℹ️ Additional Findings (2)
|













Summary
executionMode: 'durable'on all Slack chat requests, overriding per-agent config/run/api/chatand post the result back to the Slack thread