Skip to content

fix(test): share one top-level scope for httpapi-exercise effect mode - #206

Merged
alltomatos merged 1 commit into
devfrom
claude/fix-effect-mode-session-prompt
Sep 12, 2026
Merged

alltomatos merged 1 commit into
devfrom
claude/fix-effect-mode-session-prompt

Conversation

@alltomatos

Copy link
Copy Markdown
Owner

Issue for this PR

Closes #205

Type of change

  • Bug fix

What does this PR do?

Fixes the 3 effect-mode bun run test:httpapi scenarios (session.prompt, session.prompt_async, session.command) that have never been reliably green since they were added in #203.

Root cause (test harness, not app regression): test/server/httpapi-exercise/runner.ts's withContext builds the shared AppLayer via Layer.buildWithMemoMap(modules.AppLayer, modules.memoMap, scope). modules.memoMap is a single Layer.MemoMap created once for the whole exerciser process (intentional, for speed — the app is built once and reused across all 245+ scenarios). But the scope passed alongside it was obtained fresh per scenario (inside each scenario's own Effect.scoped). Since the memo map only builds the layer once, whichever scenario happens to trigger that first build permanently ties every layer-build-time yield* Scope.Scope capture (used by SessionPrompt's title-generation/auto-summarize forks and the promptAsync httpapi handler's fire-and-forget fork) to that scenario's scope. The instant that scenario's own scope closes, every later Effect.forkIn(scope, ...) anywhere in the app forks into an already-closed scope and is immediately interrupted — so session.prompt_async/session.command never reached the fake LLM server at all (30s timeouts), and session.prompt could intermittently read a leaked background fiber's fake-LLM text instead of its own.

Fix: obtain one top-level Scope once in main() (test/server/httpapi-exercise/index.ts) and thread it through runScenario → runActive → withContext for the Layer.buildWithMemoMap call, instead of a fresh per-scenario scope. This matches how the app is actually scoped in production (built once against the server's own lifetime scope).

Both changed files are test-only (test/server/httpapi-exercise/index.ts, test/server/httpapi-exercise/runner.ts). No production code was touched.

How did you verify your code works?

  • session.prompt, session.prompt_async, session.command pass reliably — verified 3x each in isolation and as part of the batch that previously reproduced the failure (--include session.prompt, which selects all three).
  • Full bun run script/httpapi-exercise.ts --mode effect --fail-on-missing --fail-on-skip: 242 pass / 3 fail. The 3 remaining failures (pty.create, worktree.reset, v2.pty.create) are pre-existing and unrelated — confirmed present before and after this change, untouched here.
  • bun run script/httpapi-exercise.ts --mode coverage --fail-on-missing --fail-on-skip: 245/245 pass (no regression).
  • bun run script/httpapi-exercise.ts --mode auth --fail-on-missing --fail-on-skip: 245/245 pass (no regression).
  • bun turbo typecheck: passes (30/30 packages).

Screenshots / recordings

N/A (test infrastructure change, no UI).

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

🤖 Generated with Claude Code

The AppLayer is memoized across every httpapi-exercise scenario for
speed, but withContext was building it against a fresh per-scenario
Scope each time. Because the memo map only builds the layer once, any
service that captures `yield* Scope.Scope` at layer-build time to fork
background work (SessionPrompt's title/summarize forks, the
promptAsync httpapi handler's fire-and-forget fork) got permanently
tied to whichever scenario happened to trigger the first build. Once
that scenario's own scope closed, every later forkIn(scope) anywhere
in the app silently died, so session.prompt_async and session.command
never reached the fake LLM server (30s timeouts), and session.prompt
could intermittently read a leaked background fiber's fake-LLM text.

Give the whole exerciser run one top-level scope in main() and thread
it through runScenario/runActive/withContext for the
Layer.buildWithMemoMap call instead, matching how the app is scoped in
production (built once against the server's own lifetime scope).

Closes #205

Co-Authored-By: Claude Sonnet 5 <[email protected]>
@alltomatos
alltomatos merged commit cf79e0f into dev Sep 12, 2026
9 of 10 checks passed
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.

httpapi-exercise effect mode: session.prompt_async/session.command hang, session.prompt gets wrong LLM text

1 participant