Skip to content

fix(api): return prompt error response instead of hanging when a SAS … - #389

Merged
allanbowe merged 3 commits into
mainfrom
issue-388
Jul 14, 2026
Merged

fix(api): return prompt error response instead of hanging when a SAS …#389
allanbowe merged 3 commits into
mainfrom
issue-388

Conversation

@YuryShkoda

Copy link
Copy Markdown
Member

Issue

Closes #388.

Stored Programs / code execution requests never respond when the underlying
SAS session fails (e.g. %abort;). The log file gets written up to the
point of failure, but the HTTP request just hangs forever — no crash, no
timeout, no response.

Intent

Make execution requests always resolve, whether the submitted code
completes normally or the session fails abnormally — surfacing a prompt
error response (with the complete SAS log attached) instead of hanging.

Implementation

Root cause: the SAS-runtime poll loop in processProgram.ts only
checked for SessionState.completed:

while (session.state !== SessionState.completed) {
  await delay(50)
}

It never checked for SessionState.failed, so once a session failed the
loop spun on delay(50) forever and the request never resolved.

Fix:

  • processProgram.ts — throws when session.state becomes failed.
  • Execution.ts — catches that around the processProgram call, reads the
    log (guaranteed complete at that point, since the session's process has
    already exited by the time failed is observable), and throws a new
    SessionExecutionError { message, log }.
  • stp.ts / code.ts — both existing error-response catch blocks now
    include log in the HTTP error body alongside the pre-existing
    { code: 400, status: 'failure', message, error } shape.

Tests (no SAS runtime available, so built around a mock in place of one):

  • processProgram.spec.ts — unit test on the poll loop directly. Genuinely
    reproduced the bug pre-fix: the "abort" case hit Jest's per-test timeout,
    and because the (buggy) loop keeps scheduling fresh timers, the whole
    Jest worker never exited on its own.
  • Execution.spec.ts — unit test for the log-enrichment/error-wrapping
    logic (mocks processProgram, matches this repo's existing mocking
    convention from stp.spec.ts).
  • routes/api/spec/files/mockSas.js — a minimal fake "SAS executable"
    (plain Node script) that fulfills just the SYSIN/AUTOEXEC filesystem
    handshake Session.ts relies on, without interpreting real SAS syntax.
    Validated directly via execFile before use.
  • code.spec.ts — end-to-end test against POST /SASjsApi/code/execute
    (the endpoint Studio's "run code" hits) using the real, unmocked
    SASSessionController + processProgram pipeline wired to the mock
    executable. Covers both the happy path (200) and the regression case
    (%abort; → prompt 400 with the complete log, not a hang).

Full suite: 14/14 test suites, 242/242 tests passing. tsc --noEmit clean.

Docs: added api/docs/diagrams/ — Mermaid diagrams of the session
lifecycle, the SAS SYSIN/AUTOEXEC execution handshake, and the end-to-end
request flow, with embedded file:line references. Written for fast
context-loading (by humans or AI agents) on this otherwise non-obvious
subsystem; not specific to this bug.

Checks

  • Code is formatted correctly (npm run lint:fix).
  • Any new functionality has been unit tested.
  • All unit tests are passing (npm test).
  • All CI checks are green.
  • Reviewer is assigned.

…session fails

The SAS-runtime poll loop in processProgram only checked for
SessionState.completed, so a session that failed (e.g. via %abort;)
left the loop spinning on delay(50) forever - the request never
resolved, even though the SAS log had already been written. Split a single completed-on-either-outcome
flag into separate completed/failed states without updating this loop.

- processProgram now throws when session.state becomes 'failed'
- Execution.ts catches that, reads the complete log (guaranteed
  complete since the session's process has already exited by then),
  and throws a SessionExecutionError carrying it
- stp.ts/code.ts surface { ..., log } in the HTTP error response
- add unit tests for the poll loop and the log-enrichment logic, plus
  a mock SAS executable + end-to-end test exercising the real
  session/process pipeline without needing a SAS install
- add docs/diagrams covering the session lifecycle and the SAS
  execution handshake mechanism, for future context
@YuryShkoda YuryShkoda self-assigned this Jul 13, 2026
- wrap the file read/write in a small retry() helper instead of
  letting an uncaught exception (e.g. a rename racing an existsSync
  check) crash the process with a non-zero exit indistinguishable
  from a genuine SAS failure
- bump the internal give-up deadline from 1500ms to 8000ms
- bump the corresponding Jest test timeouts from 15000ms to 30000ms to match
@allanbowe
allanbowe merged commit 63086a8 into main Jul 14, 2026
6 checks passed
@allanbowe
allanbowe deleted the issue-388 branch July 14, 2026 06:35
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 0.39.5 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Stored Programs fail to respond in API when SAS errors

2 participants