Skip to content

fix: Set final statusMessage before storeTaskResult (#638)#680

Open
jirispilka wants to merge 14 commits intomasterfrom
fix/task-final-status-message-638
Open

fix: Set final statusMessage before storeTaskResult (#638)#680
jirispilka wants to merge 14 commits intomasterfrom
fix/task-final-status-message-638

Conversation

@jirispilka
Copy link
Copy Markdown
Collaborator

@jirispilka jirispilka commented Apr 15, 2026

Summary

Fixes #638tasks/list shows stale statusMessage (e.g. "Starting the crawler.") for completed tasks because storeTaskResult() doesn't accept a statusMessage parameter.

Changes:

  • Add storeTaskResultWithMessage() helper in src/mcp/utils.ts — encapsulates the SDK workaround (updateTaskStatus + storeTaskResult in sequence)
  • Set correct final statusMessage per outcome in executeToolAndUpdateTask:
    • SUCCEEDED"tool: completed"
    • SOFT_FAIL"[error] tool: Actor not found…" (with [error] prefix)
    • Pre-flight 402"[error] tool: payment required"
    • Catch-block 402"[error] tool: payment required" + isTaskCancelled guard
    • Catch-block error"[error] tool: error text… (truncated)" if >200 chars
    • ABORTED (signal) → transition to cancelled (was incorrectly stored as completed with empty {} result)
  • Cancel message includes tool/actor prefix when available (e.g. "apify/rag-web-browser: cancelled by client")

SDK workaround — all results stored as 'completed':

The MCP SDK's requestStream() only calls getTaskResult() for 'completed' tasks. For 'failed' tasks it yields a generic "Task X failed" error and discards the stored result. This means:

  • x402 auto-pay retry breaks (client never sees the payment payload)
  • Error details are lost (client gets generic message instead of actual error text)

We work around this by storing all results as 'completed' and using [error] prefix in statusMessage to signal the real outcome. Full analysis in res/task_status_workaround.md.

Verified with mcpc

Path Result delivered? isError tasks-list status statusMessage
Success (actor completes) yes null completed call-actor: completed
Actor not found (soft-fail) yes, error text true completed [error] call-actor: Actor not found…
Input validation (soft-fail) yes, error text true completed [error] call-actor: Input validation failed…
Cancel (before progress) N/A N/A cancelled Cancelled by client
Cancel (after progress) N/A N/A cancelled apify/google-search-scraper: cancelled by client
Cancel completed (409) rejected N/A unchanged unchanged

Follow up issues

Test plan

  • npm run type-check — zero errors
  • npm run lint — zero errors
  • npm run test:unit — 488 tests pass
  • mcpc: success path — result delivered, statusMessage: "call-actor: completed"
  • mcpc: soft-fail — result delivered with isError: true, statusMessage has [error] prefix
  • mcpc: cancel — correct cancelled status with tool prefix when available
  • mcpc: cancel completed task — 409 rejection
  • npm run test:integration

🤖 Generated with Claude Code

@github-actions github-actions bot added t-ai Issues owned by the AI team. tested Temporary label used only programatically for some analytics. labels Apr 15, 2026
@jirispilka jirispilka changed the title fix: set final statusMessage before storeTaskResult (#638) fix: Set final statusMessage before storeTaskResult (#638) Apr 15, 2026
Comment thread src/mcp/server.ts Outdated
Comment thread src/mcp/server.ts
claude and others added 3 commits April 16, 2026 08:06
storeTaskResult() has no statusMessage parameter, so the last
intermediate progress message (e.g. "Starting the crawler.") persisted
as the final statusMessage on completed/failed tasks. Call
updateTaskStatus() with a descriptive final message right before each
storeTaskResult() call.

https://claude.ai/code/session_01ABYjPsZxtCdib54jfGcx3f
The third test asserted that a stale intermediate statusMessage is preserved
through storeTaskResult — which tests InMemoryTaskStore internals, not the
bug. The two remaining cases are sufficient regression coverage.
@jirispilka jirispilka force-pushed the fix/task-final-status-message-638 branch from ad5d65e to 9a234ab Compare April 16, 2026 07:46
jirispilka and others added 5 commits April 16, 2026 09:59
The MCP SDK's requestStream() only calls getTaskResult() for 'completed'
tasks — 'failed' tasks yield a generic "Task X failed" error and discard
the stored result. This broke x402 auto-pay retry (client never saw the
payment payload) and lost error details for soft-fail results.

- Revert task status from 'failed' back to 'completed' for all result paths
- Add [error] prefix to statusMessage so tasks/list shows the real outcome
- Add "… (truncated)" suffix when statusMessage is clipped to 200 chars
- Document the workaround and upstream fixes needed in res/task_status_workaround.md

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@jirispilka
Copy link
Copy Markdown
Collaborator Author

@claude can you review again

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes stale statusMessage for terminal tasks by ensuring a final message is written immediately before persisting the task result, along with documenting (and testing) the SDK-driven workaround for result delivery.

Changes:

  • Added storeTaskResultWithMessage() helper to update final statusMessage then store the result.
  • Updated executeToolAndUpdateTask() to set outcome-specific final messages and to mark signal aborts as cancelled.
  • Added unit + integration regression tests and documentation for the “store errors as completed” workaround.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/mcp/utils.ts Adds storeTaskResultWithMessage() helper for final statusMessage + result persistence.
src/mcp/server.ts Uses helper to set final messages; adjusts cancellation messaging and terminal handling.
tests/unit/task.statusMessage.test.ts New unit regression coverage for final statusMessage behavior.
tests/unit/server.task_execution.test.ts Verifies task-mode soft-fail payloads are stored/delivered.
tests/integration/suite.ts Integration regression test ensuring success ends with : completed.
res/task_status_workaround.md Documents why errors are stored as completed due to SDK requestStream() behavior.
res/index.md Index entry for the new workaround doc.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/mcp/server.ts Outdated
Comment thread src/mcp/server.ts Outdated
Comment thread tests/unit/task.statusMessage.test.ts Outdated
Comment thread src/mcp/server.ts Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/mcp/server.ts Outdated
Comment thread src/dev_server.ts
Comment thread src/mcp/server.ts
Comment thread src/mcp/server.ts
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/mcp/utils.ts
Comment thread src/mcp/server.ts
Comment thread res/task_status_workaround.md
Comment thread tests/unit/task.statusMessage.test.ts Outdated
Comment thread tests/unit/server.task_execution.test.ts
@jirispilka
Copy link
Copy Markdown
Collaborator Author

@copilot apply changes based on the comments in this thread

Agent-Logs-Url: https://github.com/apify/apify-mcp-server/sessions/b6debe5d-50b7-4b92-9b85-8ab365c2df09

Co-authored-by: jirispilka <19406805+jirispilka@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 16, 2026

@copilot apply changes based on the comments in this thread

Done in 558070b. I applied the thread feedback: narrowed storeTaskResultWithMessage() to completed-only, changed safeStoreResult() to rethrow non-cancellation storage errors, renamed the misleading unit-test helper, and aligned the task request tool name with call-actor in the execution test.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

src/mcp/server.ts:1147

  • finishTaskTracking logs toolName: tool.name, while status messages and telemetry initialization use toolFullName (canonical tool/actor name). This can make task-mode logs inconsistent with non-task mode (e.g. actor tools logging the encoded tool name rather than apify/<actor>). Consider passing toolFullName into logToolCallAndTelemetry here for consistency.
        const finishTaskTracking = (status: ToolStatus, diagnostics?: CallDiagnostics) => {
            this.logToolCallAndTelemetry({
                toolName: tool.name,
                mcpSessionId,
                toolStatus: status,

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/mcp/utils.ts
Comment on lines +61 to +67
export async function storeTaskResultWithMessage(
taskStore: TaskStore,
taskId: string,
// Always 'completed' — the SDK's requestStream() only delivers results for 'completed' tasks;
// 'failed' tasks yield a generic error and discard the stored result. See res/task_status_workaround.md.
status: 'completed',
result: Result,
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

storeTaskResultWithMessage requires callers to pass a status: 'completed' argument even though the type only permits 'completed' and the function comment states it’s always completed. Dropping the parameter (or renaming it to something like completedStatus is unnecessary) would reduce call-site noise and prevent the API from looking more flexible than it is.

Copilot uses AI. Check for mistakes.
Comment thread src/dev_server.ts
process.exit(1);
}

log.setLevel(process.env.LOG_LEVEL === 'info' ? log.LEVELS.INFO : log.LEVELS.DEBUG);
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This LOG_LEVEL handling only distinguishes 'info' vs “everything else → debug”. Since the repo already defines LOG_LEVEL_MAP (debug/info/notice/…/emergency), consider reusing it here so LOG_LEVEL=error (etc.) behaves as expected in dev mode, and default to a sensible level when unset.

Suggested change
log.setLevel(process.env.LOG_LEVEL === 'info' ? log.LEVELS.INFO : log.LEVELS.DEBUG);
const configuredLogLevel = (process.env.LOG_LEVEL ?? 'debug').trim().toUpperCase() as keyof typeof log.LEVELS;
const resolvedLogLevel = configuredLogLevel in log.LEVELS
? log.LEVELS[configuredLogLevel]
: log.LEVELS.DEBUG;
log.setLevel(resolvedLogLevel);

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

t-ai Issues owned by the AI team. tested Temporary label used only programatically for some analytics.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

tasks/list shows stale statusMessage for completed tasks

5 participants