Skip to content

refactor(workflow-executor): suppress spurious timeout log when step fails before timeout#1587

Merged
Scra3 merged 3 commits into
feat/prd-214-server-step-mapperfrom
fix/workflow-executor-fix-spurious-timeout-log
May 20, 2026
Merged

refactor(workflow-executor): suppress spurious timeout log when step fails before timeout#1587
Scra3 merged 3 commits into
feat/prd-214-server-step-mapperfrom
fix/workflow-executor-fix-spurious-timeout-log

Conversation

@Scra3
Copy link
Copy Markdown
Member

@Scra3 Scra3 commented May 19, 2026

Summary

  • runWithTimeout() attached a .catch() on execPromise before Promise.race to prevent UnhandledPromiseRejection on late rejections. It always logged "Step work rejected after timeout — result discarded" — even when the step failed before the timeout fired, producing a misleading log for any normal step error.
  • Added a timeoutFired boolean flag set to true only inside the setTimeout callback. The .catch() now skips the log when timeoutFired is false (rejection propagates normally through the race; no log needed).
  • Added a regression test: verifies the log is NOT emitted when execPromise rejects before the timeout.

Test plan

  • yarn workspace @forestadmin/workflow-executor test --testPathPattern="base-step-executor" — all 30 tests green including the new one
  • Existing test 'logs a late rejection of the losing promise as info' still passes (real timeout case unchanged)
  • New test 'does not log when execPromise rejects before the timeout fires' passes

🤖 Generated with Claude Code

Note

Fix spurious 'result discarded' log in BaseStepExecutor.runWithTimeout when step fails before timeout

Adds a timeoutFired boolean flag in runWithTimeout to distinguish between a step rejection caused by the timeout branch versus a normal pre-timeout failure. The 'Step work rejected after timeout — result discarded' log is now only emitted when the timeout actually fires, not on every rejection. A new test in base-step-executor.test.ts asserts that pre-timeout rejections do not produce this log.

Changes since #1587 opened

  • Renamed internal timeout tracking flag and updated rejection logging conditions [d0a5054]

Macroscope summarized 191ddf0.

… before timeout fires

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@qltysh
Copy link
Copy Markdown

qltysh Bot commented May 19, 2026

Qlty


Coverage Impact

This PR will not change total coverage.

Modified Files with Diff Coverage (1)

RatingFile% DiffUncovered Line #s
Coverage rating: A Coverage rating: A
packages/workflow-executor/src/executors/base-step-executor.ts100.0%
Total100.0%
🚦 See full report on Qlty Cloud »

🛟 Help
  • Diff Coverage: Coverage for added or modified lines of code (excludes deleted files). Learn more.

  • Total Coverage: Coverage for the whole repository, calculated as the sum of all File Coverage. Learn more.

  • File Coverage: Covered Lines divided by Covered Lines plus Missed Lines. (Excludes non-executable lines including blank lines and comments.)

    • Indirect Changes: Changes to File Coverage for files that were not modified in this PR. Learn more.

…bleExecutor in timeout test

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

// .catch() prevents UnhandledPromiseRejection when execPromise rejects after the timeout has
// already won the race. Only log when timeoutFired — a rejection before the timeout propagates
// normally through the race and needs no log here.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I think the code is clear enough that the comment isn't necessary, but it's just a preference

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Oherwise Claude says : New comment partially restates the .catch()/UnhandledPromiseRejection rationale already explained in the comment at lines 168–170; could be trimmed to focus only on the timeoutFired guard rationale — file: packages/workflow-executor/src/executors/base-step-executor.ts:179-181

if (!timeoutMs || timeoutMs <= 0) return this.doExecute();

let timer: NodeJS.Timeout | undefined;
let timeoutFired = false;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

hasTimeoutFired would be clearer

}
}, 5_000);

it('does not log when execPromise rejects before the timeout fires', async () => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Test name should express functional intent, for example : does not log discard message when step rejects before timeout - preferential

@hercemer42
Copy link
Copy Markdown

Considering it's just a log, do we need 'fix' in the commit name ? You could put 'refactor' - which would avoid triggering a build. No big deal.

…trim comment, rename test

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@Scra3 Scra3 changed the title fix(workflow-executor): suppress spurious timeout log when step fails before timeout refactor(workflow-executor): suppress spurious timeout log when step fails before timeout May 20, 2026
@Scra3 Scra3 merged commit a7eb662 into feat/prd-214-server-step-mapper May 20, 2026
30 checks passed
@Scra3 Scra3 deleted the fix/workflow-executor-fix-spurious-timeout-log branch May 20, 2026 10:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants