Skip to content
Open
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
3 changes: 3 additions & 0 deletions core/config/profile/LocalProfileLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ export default class LocalProfileLoader implements IProfileLoader {
packageIdentifier: {
uriType: "file",
fileUri: this.overrideAssistantFile?.path ?? getPrimaryConfigFilePath(),
// Pass pre-read content to bypass fs.readFileSync, which fails for
// vscode-remote:// URIs when Windows host connects to WSL (#10450)
content: this.overrideAssistantFile?.content,
},
});

Expand Down
70 changes: 70 additions & 0 deletions core/config/profile/LocalProfileLoader.vitest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { describe, expect, it, vi } from "vitest";

import { ControlPlaneClient } from "../../control-plane/client.js";
import { LLMLogger } from "../../llm/logger.js";
import { testIde } from "../../test/fixtures.js";
import LocalProfileLoader from "./LocalProfileLoader.js";

// Mock doLoadConfig to capture the arguments it receives
const mockDoLoadConfig = vi.fn().mockResolvedValue({
config: undefined,
errors: [],
configLoadInterrupted: false,
});

vi.mock("./doLoadConfig.js", () => ({
default: (...args: any[]) => mockDoLoadConfig(...args),
}));

describe("LocalProfileLoader", () => {
const controlPlaneClient = new ControlPlaneClient(
Promise.resolve(undefined),
testIde,
);
const llmLogger = new LLMLogger();

it("should pass pre-read content in packageIdentifier for override files", async () => {
const overrideFile = {
path: "vscode-remote://wsl+Ubuntu/home/user/.continue/agents/test.yaml",
content: "name: Test\nversion: 1.0.0\nschema: v1\n",
};

const loader = new LocalProfileLoader(
testIde,
controlPlaneClient,
llmLogger,
overrideFile,
);

await loader.doLoadConfig();

expect(mockDoLoadConfig).toHaveBeenCalledWith(
expect.objectContaining({
packageIdentifier: expect.objectContaining({
uriType: "file",
fileUri: overrideFile.path,
content: overrideFile.content,
}),
}),
);
});

it("should not include content in packageIdentifier when no override file", async () => {
const loader = new LocalProfileLoader(
testIde,
controlPlaneClient,
llmLogger,
);

await loader.doLoadConfig();

expect(mockDoLoadConfig).toHaveBeenCalledWith(
expect.objectContaining({
packageIdentifier: expect.objectContaining({
uriType: "file",
content: undefined,
}),
}),
);
});
});
10 changes: 9 additions & 1 deletion core/config/profile/doLoadConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,15 @@ export default async function doLoadConfig(options: {
let errors: ConfigValidationError[] | undefined;
let configLoadInterrupted = false;

if (overrideConfigYaml || fs.existsSync(configYamlPath)) {
const hasPreReadContent =
packageIdentifier.uriType === "file" &&
packageIdentifier.content !== undefined;

if (
overrideConfigYaml ||
hasPreReadContent ||
fs.existsSync(configYamlPath)
) {
const result = await loadContinueConfigFromYaml({
ide,
ideSettings,
Expand Down
185 changes: 185 additions & 0 deletions core/config/profile/doLoadConfig.vitest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
import { describe, expect, it, vi } from "vitest";

import type { PackageIdentifier } from "@continuedev/config-yaml";

// Mock heavy dependencies before importing doLoadConfig
const stubConfig = {
models: [],
rules: [],
tools: [],
slashCommands: [],
contextProviders: [],
modelsByRole: { chat: [], edit: [], apply: [], summarize: [], rerank: [] },
selectedModelByRole: {},
mcpServerStatuses: [],
allowAnonymousTelemetry: false,
experimental: {},
};
const mockLoadYaml = vi.fn().mockResolvedValue({
config: { ...stubConfig },
errors: [],
configLoadInterrupted: false,
});
const mockLoadJson = vi.fn().mockResolvedValue({
config: { ...stubConfig },
errors: [],
configLoadInterrupted: false,
});

vi.mock("../yaml/loadYaml", () => ({
loadContinueConfigFromYaml: (...args: any[]) => mockLoadYaml(...args),
}));
vi.mock("../load", () => ({
loadContinueConfigFromJson: (...args: any[]) => mockLoadJson(...args),
}));
vi.mock("../migrateSharedConfig", () => ({
migrateJsonSharedConfig: vi.fn(),
}));
vi.mock("../getWorkspaceContinueRuleDotFiles", () => ({
getWorkspaceContinueRuleDotFiles: vi
.fn()
.mockResolvedValue({ rules: [], errors: [] }),
}));
vi.mock("../markdown/loadMarkdownRules", () => ({
loadMarkdownRules: vi.fn().mockResolvedValue({ rules: [], errors: [] }),
}));
vi.mock("../markdown/loadCodebaseRules", () => ({
CodebaseRulesCache: { getInstance: () => ({ rules: [], errors: [] }) },
}));
vi.mock("../selectedModels", () => ({
rectifySelectedModelsFromGlobalContext: (c: any) => c,
}));
vi.mock("../../context/mcp/MCPManagerSingleton", () => ({
MCPManagerSingleton: { getInstance: () => ({ getStatuses: () => [] }) },
}));
vi.mock("../../tools", () => ({
getConfigDependentToolDefinitions: vi.fn().mockResolvedValue([]),
}));
vi.mock("../../tools/callTool", () => ({
encodeMCPToolUri: vi.fn(),
}));
vi.mock("../../tools/mcpToolName", () => ({
getMCPToolName: vi.fn(),
}));
vi.mock("../../util/posthog", () => ({
Telemetry: { setup: vi.fn() },
}));
vi.mock("../../util/sentry/SentryLogger", () => ({
SentryLogger: { setup: vi.fn() },
}));
vi.mock("../../util/tts", () => ({
TTS: { setup: vi.fn() },
}));
vi.mock("../../util/GlobalContext", () => ({
GlobalContext: class {
get() {
return {};
}
update() {}
},
}));
vi.mock("../../control-plane/env", () => ({
getControlPlaneEnv: vi.fn().mockResolvedValue({
DEFAULT_CONTROL_PLANE_PROXY_URL: "https://proxy.example.com/",
}),
getControlPlaneEnvSync: vi.fn().mockReturnValue({
CONTROL_PLANE_URL: "https://api.example.com/",
}),
}));
vi.mock("../../control-plane/PolicySingleton", () => ({
PolicySingleton: { getInstance: () => ({ policy: null }) },
}));
vi.mock("../../control-plane/TeamAnalytics", () => ({
TeamAnalytics: { setup: vi.fn(), shutdown: vi.fn() },
}));
vi.mock("../../promptFiles/initPrompt", () => ({
initSlashCommand: { name: "init", description: "init" },
}));

// Mock fs.existsSync to simulate missing file on disk
vi.mock("fs", async (importOriginal) => {
const actual = await importOriginal<typeof import("fs")>();
return {
...actual,
default: {
...actual,
existsSync: vi.fn().mockReturnValue(false),
},
};
});

import doLoadConfig from "./doLoadConfig.js";

const mockIde = {
getIdeInfo: vi.fn().mockResolvedValue({
ideType: "vscode",
name: "VS Code",
version: "1.90.0",
remoteName: "wsl",
extensionVersion: "1.3.31",
}),
getUniqueId: vi.fn().mockResolvedValue("test-id"),
getIdeSettings: vi.fn().mockResolvedValue({}),
showToast: vi.fn(),
isTelemetryEnabled: vi.fn().mockResolvedValue(true),
isWorkspaceRemote: vi.fn().mockResolvedValue(true),
} as any;

const mockControlPlaneClient = {
getAccessToken: vi.fn().mockResolvedValue("token"),
isSignedIn: vi.fn().mockResolvedValue(false),
sessionInfoPromise: Promise.resolve(undefined),
} as any;

const mockLlmLogger = {} as any;

describe("doLoadConfig pre-read content bypass", () => {
it("should use YAML loading when packageIdentifier has pre-read content, even if file does not exist on disk", async () => {
mockLoadYaml.mockClear();
mockLoadJson.mockClear();

const packageIdentifier: PackageIdentifier = {
uriType: "file",
fileUri:
"vscode-remote://wsl+Ubuntu/home/user/.continue/agents/test.yaml",
content: "name: Test\nversion: 1.0.0\nschema: v1\n",
};

await doLoadConfig({
ide: mockIde,
controlPlaneClient: mockControlPlaneClient,
llmLogger: mockLlmLogger,
profileId: "test-profile",
overrideConfigYamlByPath: packageIdentifier.fileUri,
orgScopeId: null,
packageIdentifier,
});

expect(mockLoadYaml).toHaveBeenCalled();
expect(mockLoadJson).not.toHaveBeenCalled();
});

it("should fall back to JSON loading when no content and file does not exist", async () => {
mockLoadYaml.mockClear();
mockLoadJson.mockClear();

const packageIdentifier: PackageIdentifier = {
uriType: "file",
fileUri:
"vscode-remote://wsl+Ubuntu/home/user/.continue/agents/test.yaml",
};

await doLoadConfig({
ide: mockIde,
controlPlaneClient: mockControlPlaneClient,
llmLogger: mockLlmLogger,
profileId: "test-profile",
overrideConfigYamlByPath: packageIdentifier.fileUri,
orgScopeId: null,
packageIdentifier,
});

expect(mockLoadYaml).not.toHaveBeenCalled();
expect(mockLoadJson).toHaveBeenCalled();
});
});
Loading