fix: Better remote check for auth methods (#538)

The auth methods don't have interactive modes, which means they don't
work on remote machines when you need to paste a code in.

Our previous change might not have been enough, so added some heuristics
similar to what the SDK does for checking for remote.

It's not perfect because I think they are attempting to open the URL and
then falling back when it fails, but heopfully this is at least closer.
This commit is contained in:
Ben Brandt
2026-04-13 17:34:27 +02:00
committed by GitHub
parent 6811943f57
commit 93f58c0d2f
2 changed files with 36 additions and 8 deletions

View File

@@ -339,13 +339,21 @@ export class ClaudeAcpAgent implements Agent {
const supportsTerminalAuth = request.clientCapabilities?.auth?.terminal === true;
const supportsMetaTerminalAuth = request.clientCapabilities?._meta?.["terminal-auth"] === true;
const noBrowser = !!process.env.NO_BROWSER;
// When NO_BROWSER is set (e.g. remote environments), fall back to the single
// terminal-only login that doesn't try to open a browser.
// Detect remote environments where the OAuth browser redirect to localhost
// won't work. This matches the SDK's internal isRemote check. In these cases,
// the `auth login` subcommand would fall back to a device-code-like manual
// flow, which doesn't work well over ACP, so we offer the TUI login instead.
const isRemote = !!(
process.env.NO_BROWSER ||
process.env.SSH_CONNECTION ||
process.env.SSH_CLIENT ||
process.env.SSH_TTY ||
process.env.CLAUDE_CODE_REMOTE
);
const terminalAuthMethods: AuthMethod[] = [];
if (noBrowser) {
if (isRemote) {
const remoteLoginMethod: AuthMethod = {
description: "Run `claude /login` in the terminal",
name: "Log in with Claude",

View File

@@ -169,9 +169,9 @@ describe("authorization", () => {
);
});
it("NO_BROWSER falls back to single legacy login method", async () => {
it("SSH session falls back to single legacy login method", async () => {
const [agent] = await createAgentMock();
vi.stubGlobal("process", { ...process, env: { ...process.env, NO_BROWSER: "1" } });
vi.stubGlobal("process", { ...process, env: { ...process.env, SSH_TTY: "/dev/pts/0" } });
const initializeResponse = await agent.initialize({
protocolVersion: 1,
@@ -189,12 +189,32 @@ describe("authorization", () => {
);
});
it("NO_BROWSER respects hide-claude-auth", async () => {
it("CLAUDE_CODE_REMOTE falls back to single legacy login method", async () => {
const [agent] = await createAgentMock();
vi.stubGlobal("process", { ...process, env: { ...process.env, CLAUDE_CODE_REMOTE: "1" } });
const initializeResponse = await agent.initialize({
protocolVersion: 1,
clientCapabilities: { auth: { terminal: true } },
});
expect(initializeResponse.authMethods).toContainEqual(
expect.objectContaining({ id: "claude-login" }),
);
expect(initializeResponse.authMethods).not.toContainEqual(
expect.objectContaining({ id: "claude-ai-login" }),
);
expect(initializeResponse.authMethods).not.toContainEqual(
expect.objectContaining({ id: "console-login" }),
);
});
it("remote environment respects hide-claude-auth", async () => {
const [agent] = await createAgentMock();
vi.stubGlobal("process", {
...process,
argv: ["--hide-claude-auth"],
env: { ...process.env, NO_BROWSER: "1" },
env: { ...process.env, SSH_CONNECTION: "192.168.1.1 12345 192.168.1.2 22" },
});
const initializeResponse = await agent.initialize({