Skip to content

Commit 0592b59

Browse files
github-actions[bot]devin-ai-integration[bot]
authored andcommitted
fix(server): treat DEVIN_API_KEY as ambient devin credentials
The CLI also honors DEVIN_API_KEY (Cognition REST tokens, apk_/cog_ prefixes) for headless auth. Without it in the ambient check, a signed-in user with only that env var would get a devin-browser popup on thread start. Signed-out test environments blank it so host envs can't leak in. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1 parent da53f0f commit 0592b59

5 files changed

Lines changed: 28 additions & 7 deletions

File tree

‎apps/server/src/provider/Layers/DevinAdapter.test.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ function signedOutEnvironment(home: string): NodeJS.ProcessEnv {
127127
...process.env,
128128
HOME: home,
129129
WINDSURF_API_KEY: "",
130+
DEVIN_API_KEY: "",
130131
// Blank (not delete) so a machine where these are set still reads as
131132
// signed out: resolution treats empty values as unset.
132133
XDG_DATA_HOME: "",

‎apps/server/src/provider/Layers/DevinProvider.test.ts‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ it.layer(NodeServices.layer)("checkDevinProviderStatus", (it) => {
255255
});
256256
return yield* checkDevinProviderStatus(
257257
decodeDevinSettings({ enabled: true, binaryPath: devinPath }),
258-
{ ...process.env, WINDSURF_API_KEY: "" },
258+
{ ...process.env, WINDSURF_API_KEY: "", DEVIN_API_KEY: "" },
259259
);
260260
}),
261261
);
@@ -288,7 +288,7 @@ it.layer(NodeServices.layer)("checkDevinProviderStatus", (it) => {
288288
const devinPath = yield* writeFakeDevinCli({ authOutput: LOGGED_OUT_AUTH_OUTPUT });
289289
return yield* checkDevinProviderStatus(
290290
decodeDevinSettings({ enabled: true, binaryPath: devinPath }),
291-
{ ...process.env, WINDSURF_API_KEY: "" },
291+
{ ...process.env, WINDSURF_API_KEY: "", DEVIN_API_KEY: "" },
292292
);
293293
}),
294294
);
@@ -304,7 +304,7 @@ it.layer(NodeServices.layer)("checkDevinProviderStatus", (it) => {
304304
Effect.gen(function* () {
305305
const probe = yield* checkDevinProviderStatus(
306306
decodeDevinSettings({ enabled: true, binaryPath: "/nonexistent/devin-binary" }),
307-
{ ...process.env, WINDSURF_API_KEY: "" },
307+
{ ...process.env, WINDSURF_API_KEY: "", DEVIN_API_KEY: "" },
308308
);
309309

310310
expect(probe.installed).toBe(false);
@@ -346,7 +346,7 @@ it.layer(NodeServices.layer)("checkDevinProviderStatus", (it) => {
346346
});
347347
return yield* checkDevinProviderStatus(
348348
decodeDevinSettings({ enabled: true, binaryPath: devinPath }),
349-
{ ...process.env, WINDSURF_API_KEY: "" },
349+
{ ...process.env, WINDSURF_API_KEY: "", DEVIN_API_KEY: "" },
350350
);
351351
}),
352352
);

‎apps/server/src/provider/acp/DevinAcpSupport.test.ts‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,17 @@ it.layer(NodeServices.layer)("DevinAcpSupport", (it) => {
251251
}),
252252
);
253253

254+
it.effect("treats a headless DEVIN_API_KEY as ambient credentials", () =>
255+
Effect.gen(function* () {
256+
const environment = {
257+
HOME: NodePath.join(NodeOS.tmpdir(), "t3code-devin-no-such-home"),
258+
DEVIN_API_KEY: "cog_test-key",
259+
};
260+
assert.isTrue(yield* devinHasAmbientCredentials(environment));
261+
assert.isUndefined(yield* resolveDevinAuthMethodId({ environment, browserAuth: true }));
262+
}),
263+
);
264+
254265
it.effect("returns devin-browser only when logged out and browser auth is allowed", () =>
255266
Effect.gen(function* () {
256267
const fs = yield* FileSystem.FileSystem;

‎apps/server/src/provider/acp/DevinAcpSupport.ts‎

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ const DEVIN_DRIVER_KIND = ProviderDriverKind.make("devin");
3333
*/
3434
export const DEVIN_BROWSER_AUTH_METHOD = "devin-browser";
3535
export const DEVIN_API_KEY_ENV = "WINDSURF_API_KEY";
36+
/**
37+
* `devin` also honors `DEVIN_API_KEY` for headless runs (Cognition REST API
38+
* tokens, `apk_`/`cog_` prefixes). If `devin acp` ignores it the session simply
39+
* fails auth and the user is told to run `devin auth login` — the same outcome
40+
* as a stale credentials file.
41+
*/
42+
export const DEVIN_REST_API_KEY_ENV = "DEVIN_API_KEY";
3643
const DEVIN_CREDENTIALS_PATH_SEGMENTS = [".local", "share", "devin", "credentials.toml"] as const;
3744

3845
type DevinAcpRuntimeDevinSettings = Pick<DevinSettings, "binaryPath">;
@@ -64,14 +71,15 @@ export function devinCredentialsFilePaths(
6471

6572
/**
6673
* Whether the Devin CLI can authenticate without a browser round trip. Mirrors
67-
* the credential order `devin acp` itself applies: `WINDSURF_API_KEY` from the
68-
* environment first, then the `devin auth login` credentials file.
74+
* the credential order `devin acp` itself applies: `WINDSURF_API_KEY` (or the
75+
* headless `DEVIN_API_KEY`) from the environment first, then the
76+
* `devin auth login` credentials file.
6977
*/
7078
export const devinHasAmbientCredentials = (
7179
environment: NodeJS.ProcessEnv | undefined,
7280
): Effect.Effect<boolean, never, FileSystem.FileSystem> =>
7381
Effect.gen(function* () {
74-
if (environment?.[DEVIN_API_KEY_ENV]?.trim()) {
82+
if (environment?.[DEVIN_API_KEY_ENV]?.trim() || environment?.[DEVIN_REST_API_KEY_ENV]?.trim()) {
7583
return true;
7684
}
7785
const fileSystem = yield* FileSystem.FileSystem;

‎apps/server/src/textGeneration/DevinTextGeneration.test.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ it.layer(DevinTextGenerationTestLayer)("DevinTextGeneration", (it) => {
8989
...process.env,
9090
HOME: fakeHome,
9191
WINDSURF_API_KEY: "",
92+
DEVIN_API_KEY: "",
9293
XDG_DATA_HOME: NodePath.join(fakeHome, "xdg"),
9394
LOCALAPPDATA: NodePath.join(fakeHome, "localappdata"),
9495
},

0 commit comments

Comments
 (0)