Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion apps/server/src/git/Layers/GitCore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,9 @@ it.layer(TestLayer)("git integration", (it) => {

it.effect("refreshes upstream behind count after checkout when remote branch advanced", () =>
Effect.gen(function* () {
const services = yield* Effect.services();
const runPromise = Effect.runPromiseWith(services);

const remote = yield* makeTmpDir();
const source = yield* makeTmpDir();
const clone = yield* makeTmpDir();
Expand Down Expand Up @@ -449,7 +452,7 @@ it.layer(TestLayer)("git integration", (it) => {
const core = yield* GitCore;
yield* Effect.promise(() =>
vi.waitFor(async () => {
const details = await Effect.runPromise(core.statusDetails(source));
const details = await runPromise(core.statusDetails(source));
expect(details.branch).toBe(featureBranch);
expect(details.aheadCount).toBe(0);
expect(details.behindCount).toBe(1);
Expand Down
4 changes: 2 additions & 2 deletions apps/server/src/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ it.layer(testLayer)("server CLI command", (it) => {

it.effect("does not start server for invalid --mode values", () =>
Effect.gen(function* () {
yield* runCli(["--mode", "invalid"]);
yield* runCli(["--mode", "invalid"]).pipe(Effect.catch(() => Effect.void));

assert.equal(start.mock.calls.length, 0);
assert.equal(stop.mock.calls.length, 0);
Expand All @@ -386,7 +386,7 @@ it.layer(testLayer)("server CLI command", (it) => {

it.effect("does not start server for out-of-range --port values", () =>
Effect.gen(function* () {
yield* runCli(["--port", "70000"]);
yield* runCli(["--port", "70000"]).pipe(Effect.catch(() => Effect.void));

// effect/unstable/cli renders help/errors for parse failures and returns success.
assert.equal(start.mock.calls.length, 0);
Expand Down
21 changes: 17 additions & 4 deletions apps/server/src/persistence/NodeSqliteClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import * as Stream from "effect/Stream";
import * as Reactivity from "effect/unstable/reactivity/Reactivity";
import * as Client from "effect/unstable/sql/SqlClient";
import type { Connection } from "effect/unstable/sql/SqlConnection";
import { SqlError } from "effect/unstable/sql/SqlError";
import { SqlError, classifySqliteError } from "effect/unstable/sql/SqlError";
import * as Statement from "effect/unstable/sql/Statement";

const ATTR_DB_SYSTEM_NAME = "db.system.name";
Expand All @@ -29,6 +29,9 @@ export const TypeId: TypeId = "~local/sqlite-node/SqliteClient";

export type TypeId = "~local/sqlite-node/SqliteClient";

const classifyError = (cause: unknown, message: string, operation: string) =>
classifySqliteError(cause, { message, operation });

/**
* SqliteClient - Effect service tag for the sqlite SQL client.
*/
Expand Down Expand Up @@ -109,7 +112,10 @@ const makeWithDatabase = (
lookup: (sql: string) =>
Effect.try({
try: () => db.prepare(sql),
catch: (cause) => new SqlError({ cause, message: "Failed to prepare statement" }),
catch: (cause) =>
new SqlError({
reason: classifyError(cause, "Failed to prepare statement", "prepare"),
}),
}),
});

Expand All @@ -127,7 +133,11 @@ const makeWithDatabase = (
const result = statement.run(...(params as any));
return Effect.succeed(raw ? (result as unknown as ReadonlyArray<any>) : []);
} catch (cause) {
return Effect.fail(new SqlError({ cause, message: "Failed to execute statement" }));
return Effect.fail(
new SqlError({
reason: classifyError(cause, "Failed to execute statement", "execute"),
}),
);
}
});

Expand All @@ -150,7 +160,10 @@ const makeWithDatabase = (
statement.run(...(params as any));
return [];
},
catch: (cause) => new SqlError({ cause, message: "Failed to execute statement" }),
catch: (cause) =>
new SqlError({
reason: classifyError(cause, "Failed to execute statement", "execute"),
}),
}),
(statement) =>
Effect.sync(() => {
Expand Down
10 changes: 8 additions & 2 deletions apps/server/src/provider/Layers/ClaudeAdapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1100,10 +1100,13 @@ describe("ClaudeAdapterLive", () => {
it.effect("closes the session when the Claude stream aborts after a turn starts", () => {
const harness = makeHarness();
return Effect.gen(function* () {
const services = yield* Effect.services();
const runFork = Effect.runForkWith(services);

const adapter = yield* ClaudeAdapter;
const runtimeEvents: Array<ProviderRuntimeEvent> = [];

const runtimeEventsFiber = Effect.runFork(
const runtimeEventsFiber = runFork(
Stream.runForEach(adapter.streamEvents, (event) =>
Effect.sync(() => {
runtimeEvents.push(event);
Expand Down Expand Up @@ -1197,9 +1200,12 @@ describe("ClaudeAdapterLive", () => {
);

return Effect.gen(function* () {
const services = yield* Effect.services();
const runFork = Effect.runForkWith(services);

const adapter = yield* ClaudeAdapter;

const runtimeEventsFiber = Effect.runFork(
const runtimeEventsFiber = runFork(
Stream.runForEach(adapter.streamEvents, () => Effect.void),
);

Expand Down
14 changes: 9 additions & 5 deletions apps/server/src/provider/Layers/ClaudeAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2380,6 +2380,10 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (
existingResumeSessionId === undefined ? yield* Random.nextUUIDv4 : undefined;
const sessionId = existingResumeSessionId ?? newSessionId;

const services = yield* Effect.services();
const runFork = Effect.runForkWith(services);
const runPromise = Effect.runPromiseWith(services);

const promptQueue = yield* Queue.unbounded<PromptQueueItem>();
const prompt = Stream.fromQueue(promptQueue).pipe(
Stream.filter((item) => item.type === "message"),
Expand Down Expand Up @@ -2461,7 +2465,7 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (
}
aborted = true;
pendingUserInputs.delete(requestId);
Effect.runFork(Deferred.succeed(answersDeferred, {} as ProviderUserInputAnswers));
runFork(Deferred.succeed(answersDeferred, {} as ProviderUserInputAnswers));
};
callbackOptions.signal.addEventListener("abort", onAbort, { once: true });

Expand Down Expand Up @@ -2607,7 +2611,7 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (
return;
}
pendingApprovals.delete(requestId);
Effect.runFork(Deferred.succeed(decisionDeferred, "cancel"));
runFork(Deferred.succeed(decisionDeferred, "cancel"));
};

callbackOptions.signal.addEventListener("abort", onAbort, {
Expand Down Expand Up @@ -2662,7 +2666,7 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (
});

const canUseTool: CanUseTool = (toolName, toolInput, callbackOptions) =>
Effect.runPromise(canUseToolEffect(toolName, toolInput, callbackOptions));
runPromise(canUseToolEffect(toolName, toolInput, callbackOptions));

const claudeSettings = yield* serverSettingsService.getSettings.pipe(
Effect.map((settings) => settings.providers.claudeAgent),
Expand Down Expand Up @@ -2813,7 +2817,7 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (
providerRefs: {},
});

const streamFiber = Effect.runFork(runSdkStream(context));
const streamFiber = runFork(runSdkStream(context));
context.streamFiber = streamFiber;
streamFiber.addObserver((exit) => {
if (context.stopped) {
Expand All @@ -2822,7 +2826,7 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (
if (context.streamFiber === streamFiber) {
context.streamFiber = undefined;
}
Effect.runFork(handleStreamExit(context, exit));
runFork(handleStreamExit(context, exit));
});

return {
Expand Down
16 changes: 8 additions & 8 deletions apps/server/src/wsServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,11 @@ export const createServer = Effect.fn(function* (): Effect.fn.Return<
} = serverConfig;
const availableEditors = resolveAvailableEditors();

const runtimeServices = yield* Effect.services<
ServerRuntimeServices | ServerConfig | FileSystem.FileSystem | Path.Path
>();
const runPromise = Effect.runPromiseWith(runtimeServices);

const gitManager = yield* GitManager;
const terminalManager = yield* TerminalManager;
const keybindingsManager = yield* Keybindings;
Expand Down Expand Up @@ -429,7 +434,7 @@ export const createServer = Effect.fn(function* (): Effect.fn.Return<
res.end(body);
};

void Effect.runPromise(
void runPromise(
Effect.gen(function* () {
const url = new URL(req.url ?? "/", `http://localhost:${port}`);
if (tryHandleProjectFaviconRequest(url, res)) {
Expand Down Expand Up @@ -713,13 +718,8 @@ export const createServer = Effect.fn(function* (): Effect.fn.Return<
);
}

const runtimeServices = yield* Effect.services<
ServerRuntimeServices | ServerConfig | FileSystem.FileSystem | Path.Path
>();
const runPromise = Effect.runPromiseWith(runtimeServices);

const unsubscribeTerminalEvents = yield* terminalManager.subscribe(
(event) => void Effect.runPromise(pushBus.publishAll(WS_CHANNELS.terminalEvent, event)),
const unsubscribeTerminalEvents = yield* terminalManager.subscribe((event) =>
runPromise(pushBus.publishAll(WS_CHANNELS.terminalEvent, event)),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Missing void in terminal subscribe callback returns Promise

Low Severity

The terminal subscribe callback previously used void Effect.runPromise(...) to explicitly discard the returned Promise (fire-and-forget pattern). The refactored code drops the void, so the callback now returns a Promise<void> instead of undefined. The listener type is (event: TerminalEvent) => void, and while the EventEmitter ignores return values, this is inconsistent with the HTTP handler on line 437 which correctly keeps void runPromise(...). The missing void appears unintentional.

Fix in Cursor Fix in Web

);
yield* Effect.addFinalizer(() => Effect.sync(() => unsubscribeTerminalEvents()));
yield* readiness.markTerminalSubscriptionsReady;
Expand Down
30 changes: 11 additions & 19 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
"scripts"
],
"catalog": {
"effect": "https://pkg.pr.new/Effect-TS/effect-smol/effect@8881a9b",
"@effect/platform-node": "https://pkg.pr.new/Effect-TS/effect-smol/@effect/platform-node@8881a9b",
"@effect/sql-sqlite-bun": "https://pkg.pr.new/Effect-TS/effect-smol/@effect/sql-sqlite-bun@8881a9b",
"@effect/vitest": "https://pkg.pr.new/Effect-TS/effect-smol/@effect/vitest@8881a9b",
"@effect/language-service": "0.75.1",
"effect": "4.0.0-beta.42",
"@effect/platform-node": "4.0.0-beta.42",
"@effect/sql-sqlite-bun": "4.0.0-beta.42",
"@effect/vitest": "4.0.0-beta.42",
"@effect/language-service": "0.84.1",
"@types/bun": "^1.3.9",
"@types/node": "^24.10.13",
"tsdown": "^0.20.3",
Expand Down
Loading