Skip to content

fix(ci): v2.session.permission.create flaky in effect-mode httpapi-exercise #209

Description

@alltomatos

Summary

bun run script/httpapi-exercise.ts --mode effect intermittently fails the v2.session.permission.create scenario. Reproduced twice in a row in CI on PR #208 (unrelated PR). Only flaky in effect mode, which runs against the real Effect runtime — coverage and auth modes pass reliably (they don't exercise the real async plugin-loading path).

Root cause

Real race in application code, not a test-harness artifact.

PermissionV2.ask (packages/core/src/permission.ts) evaluates a permission request against the calling agent's configured ruleset (agents.resolve(...).permissions). The default agent's ruleset — including the { action: "read", resource: "*.env", effect: "ask" } rule the test exercises — is registered by AgentPlugin/ConfigAgentPlugin, which PluginInternal (packages/core/src/plugin/internal.ts) runs in a forked, non-blocking fiber immediately after a location is built, deliberately not blocking the location layer itself (see the doc comment on PluginInternal.Service).

This leaves a real window, right after a session/location is first created, where AgentV2.Service's agent registry is still empty. A permission check that races this window sees no matching agent, falls back to missingAgentPermissions (deny-all), and returns effect: "deny" instead of the expected effect: "ask" — which is exactly the assertion that fails:

Error: permission create should create a pending request
    at check (test/server/httpapi-exercise/assertions.ts:47:25)

This is the same general hazard as #205/#206 (scope-leak) and #207 (tight poll timeout), but a distinct root cause: a genuine ordering race between plugin registration and permission evaluation, not a test-harness state leak or an overly tight timeout.

The codebase already has an established pattern for this exact hazard: both packages/opencode/src/session/prompt.ts and packages/opencode/src/server/routes/instance/httpapi/handlers/provider.ts await PluginInternal.Service.ready (bounded by a 5s timeout) before reading state that plugins populate.

Fix

Apply the same pattern to the session.permission.create HTTP handler (packages/server/src/handlers/permission.ts): await PluginInternal.Service.ready (bounded, 5s timeout) before calling permission.ask(...).

(Note: the equivalent wait was not added inside packages/core/src/permission.ts itself — doing so there creates a real ES module circular-import cycle among permission.ts / plugin/internal.ts / plugin/agent.ts / skill.ts, which crashes at startup with ReferenceError: Cannot access 'node' before initialization. Waiting at the HTTP handler layer, where PluginInternal.Service is already available via the session's location context, avoids the cycle entirely.)

Verification

  • v2.session.permission.create passes 10/10 in --mode effect.
  • --mode coverage --fail-on-missing --fail-on-skip: 245/245 pass.
  • --mode auth --fail-on-missing --fail-on-skip: 245/245 pass.
  • bun turbo typecheck: passes across all packages.

🤖 Generated with Claude Code

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions