Skip to content

Commit 3fb824c

Browse files
authored
Fix ask for permissions (#268)
## Root Cause OpenCode PR [#21986](anomalyco/opencode#21986) (merged **April 10, 2026**, commit `bfef334b`) deliberately changed `ToolContext.ask` in the plugin SDK from returning `Promise<void>` to `Effect.Effect<void>`: ```diff - ask(input: AskInput): Promise<void> + ask(input: AskInput): Effect.Effect<void> ``` This broke the previous fix from commit `9e92eb3` (April 1, 2026), which used `await context.ask(...)` and worked correctly at the time. After the opencode change, `await`-ing an Effect object is a no-op — the Effect is never executed, so the permission prompt was silently skipped. ## Fix Add `effect` as a peer dependency (it is always present in the process since the plugin runs inside opencode) and a dev dependency for local type checking. Use `Effect.runPromise()` to bridge the Effect into the async/await context of the plugin's `execute` functions. The `ask` call is centralised in the `wrap()` decorator in `plugin.ts` so all five tools get permission enforcement without duplicating the call in each handler. ## Changes - `package.json`: add `effect >=3.0.0` as peer + dev dependency - `src/types.ts`: fix local `ToolContext.ask` type to match real SDK (`Effect.Effect<void>`) - `src/plugin.ts`: use `Effect.runPromise(ctx.ask(...))` in the `wrap()` decorator; import `Effect` from `effect` - `test/e2e/plugin.test.ts`: update `createMockToolContext()` helper; mock `ask` returns `Effect.void` instead of a resolved Promise
1 parent 90e9f34 commit 3fb824c

5 files changed

Lines changed: 54 additions & 16 deletions

File tree

packages/opencode-plugin/package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,21 @@
2929
"devDependencies": {
3030
"@codemcp/workflows-core": "workspace:*",
3131
"@codemcp/workflows-server": "workspace:*",
32+
"effect": "3.21.1",
3233
"rimraf": "^6.0.1",
3334
"tsup": "^8.0.0",
3435
"vitest": "4.0.18"
3536
},
3637
"peerDependencies": {
37-
"@anthropic-ai/sdk": "*"
38+
"@anthropic-ai/sdk": "*",
39+
"effect": ">=3.0.0"
3840
},
3941
"peerDependenciesMeta": {
4042
"@anthropic-ai/sdk": {
4143
"optional": true
44+
},
45+
"effect": {
46+
"optional": true
4247
}
4348
},
4449
"keywords": [

packages/opencode-plugin/src/plugin.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
*/
1414

1515
import type { Plugin, PluginInput, Hooks, ToolDefinition } from './types.js';
16+
import { Effect } from 'effect';
1617
import { createProceedToPhaseTool } from './tool-handlers/proceed-to-phase.js';
1718
import { createConductReviewTool } from './tool-handlers/conduct-review.js';
1819
import { createResetDevelopmentTool } from './tool-handlers/reset-development.js';
@@ -652,12 +653,14 @@ ACTION REQUIRED: Use transition_phase tool to move to a phase that allows editin
652653
);
653654
}
654655

655-
await ctx.ask({
656-
permission: toolName,
657-
patterns: ['*'],
658-
always: ['*'],
659-
metadata: {},
660-
});
656+
await Effect.runPromise(
657+
ctx.ask({
658+
permission: toolName,
659+
patterns: ['*'],
660+
always: ['*'],
661+
metadata: {},
662+
})
663+
);
661664

662665
return def.execute(args, ctx);
663666
},

packages/opencode-plugin/src/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ export type PluginInput = {
5151
};
5252

5353
// Tool context for custom tools
54+
import type { Effect } from 'effect';
55+
5456
export type ToolContext = {
5557
sessionID: string;
5658
messageID: string;
@@ -64,7 +66,7 @@ export type ToolContext = {
6466
patterns: string[];
6567
always: string[];
6668
metadata: Record<string, unknown>;
67-
}): Promise<void>;
69+
}): Effect.Effect<void>;
6870
};
6971

7072
// Tool definition

packages/opencode-plugin/test/e2e/plugin.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
*/
77

88
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
9+
import { Effect } from 'effect';
910
import * as fs from 'node:fs';
1011
import * as path from 'node:path';
1112
import { tmpdir } from 'node:os';
@@ -63,7 +64,7 @@ function createMockToolContext(overrides: Record<string, unknown> = {}) {
6364
worktree: '',
6465
abort: new AbortController().signal,
6566
metadata: vi.fn(),
66-
ask: vi.fn().mockResolvedValue(undefined),
67+
ask: vi.fn().mockReturnValue(Effect.void),
6768
...overrides,
6869
};
6970
}

pnpm-lock.yaml

Lines changed: 34 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)