Skip to content

Commit 434a319

Browse files
anandgupta42claude
andcommitted
fix: do not load another project's connections file on an empty-registry reload
The registry is process-global. Record the project whose local file it was loaded with; the empty-registry reload re-reads the project-local file only when the current project is that one. The global file and `ALTIMATE_CODE_CONN_*` stay process-wide. The `warehouse_list` hint advertises the project path only when it would be picked up, and says environment variables are read at process start. Co-Authored-By: Claude Opus 5.5 <[email protected]>
1 parent 55bf62b commit 434a319

3 files changed

Lines changed: 57 additions & 8 deletions

File tree

‎packages/opencode/src/altimate/native/connections/registry.ts‎

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,14 +197,17 @@ function loadFromEnv(): Record<string, ConnectionConfig> {
197197
}
198198

199199
/** Load all connection configs. Local overrides global; env overrides both. */
200-
export function load(): void {
200+
export function load(includeProjectConfig = true): void {
201201
configs.clear()
202202

203203
// altimate_change start — absolutize store paths against the directory that
204204
// declared them, so a later process.chdir() (--dir) cannot move the store.
205205
const base = projectRoot()
206206
const global = resolveStorePaths(loadFromFile(globalConfigPath()), path.dirname(globalConfigPath()))
207-
const local = resolveStorePaths(loadFromFile(localConfigPath()), base)
207+
// altimate_change start — see ensureLoaded(): a later reload must not pull in another project's file
208+
const local = includeProjectConfig ? resolveStorePaths(loadFromFile(localConfigPath()), base) : {}
209+
if (includeProjectConfig) loadedProjectDir = base
210+
// altimate_change end
208211
const env = resolveStorePaths(loadFromEnv(), base)
209212
// altimate_change end
210213

@@ -229,10 +232,23 @@ function ensureLoaded(): void {
229232
// altimate_change start — the registry latches on first load, so a connections file created after an empty
230233
// answer (the `warehouse_list` hint) was never seen. Re-read only while nothing is loaded: there is no state to
231234
// replace or close, and a populated registry never changes under a running session.
232-
else if (configs.size === 0 && !injected) load()
235+
else if (configs.size === 0 && !injected) load(projectConfigApplies())
233236
// altimate_change end
234237
}
235238

239+
// altimate_change start — the registry is process-global; this is the project whose local file it was loaded with
240+
let loadedProjectDir: string | undefined
241+
242+
/**
243+
* Whether the current project's local connections file is the one this registry reads. A server serving several
244+
* directories shares one registry (pre-existing: the first load's project-local file applies process-wide), so a
245+
* later empty-registry reload must not load another project's file into it.
246+
*/
247+
export function projectConfigApplies(): boolean {
248+
return loadedProjectDir === undefined || loadedProjectDir === projectRoot()
249+
}
250+
// altimate_change end
251+
236252
// altimate_change start — set by setConfigs() (tests): an injected empty registry is not a stale one
237253
let injected = false
238254
// altimate_change end
@@ -876,6 +892,7 @@ export function reset(): void {
876892
censusSent = false
877893
// altimate_change start
878894
injected = false
895+
loadedProjectDir = undefined
879896
// altimate_change end
880897
}
881898

‎packages/opencode/src/altimate/tools/warehouse-list.ts‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import * as Precedence from "../workspace/precedence"
66
// altimate_change end
77

88
// altimate_change start — point at the locations the registry actually reads
9-
import { CONN_ENV_PREFIX, globalConfigPath, localConfigPath } from "../native/connections/registry"
9+
import { CONN_ENV_PREFIX, globalConfigPath, localConfigPath, projectConfigApplies } from "../native/connections/registry"
1010

1111
/** The no-connections hint. Paths come from the registry's own helpers so it cannot drift from what is loaded. */
1212
export function noConnectionsMessage(): string {
@@ -17,10 +17,10 @@ export function noConnectionsMessage(): string {
1717
"",
1818
"Alternatively, connections are read from these sources (later ones override earlier ones):",
1919
` 1. ${globalConfigPath()} (global)`,
20-
` 2. ${localConfigPath()} (this project)`,
21-
` 3. ${CONN_ENV_PREFIX}<NAME> environment variables, each holding one connection's config as JSON`,
20+
...(projectConfigApplies() ? [` 2. ${localConfigPath()} (this project)`] : []),
21+
` ${projectConfigApplies() ? 3 : 2}. ${CONN_ENV_PREFIX}<NAME> environment variables, each holding one connection's config as JSON (read when the process starts)`,
2222
"",
23-
"After adding the first connection, call warehouse_list again. Edits to an existing connections.json take effect after a restart. Example connections.json:",
23+
"After adding the first connection file, call warehouse_list again. Edits to an existing connections.json take effect after a restart. Example connections.json:",
2424
"{",
2525
' "my-db": { "type": "postgres", "host": "localhost", "port": 5432, "database": "mydb", "user": "user", "password": "pass" }',
2626
"}",

‎packages/opencode/test/altimate/warehouse-list-hint.test.ts‎

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,13 @@ describe("warehouse_list no-connections hint", () => {
8989
const tool = await initTool(WarehouseListTool)
9090
const ctx = toolCtx()
9191
await tool.execute({}, ctx)
92+
const previous = process.env.ALTIMATE_CODE_CONN_ENVLATE
9293
process.env.ALTIMATE_CODE_CONN_ENVLATE = JSON.stringify({ type: "postgres", host: "h" })
9394
try {
9495
expect((await tool.execute({}, ctx)).output).toContain("envlate")
9596
} finally {
96-
delete process.env.ALTIMATE_CODE_CONN_ENVLATE
97+
if (previous === undefined) delete process.env.ALTIMATE_CODE_CONN_ENVLATE
98+
else process.env.ALTIMATE_CODE_CONN_ENVLATE = previous
9799
}
98100
})
99101

@@ -128,6 +130,36 @@ describe("warehouse_list no-connections hint", () => {
128130
}
129131
})
130132

133+
test("an empty registry first loaded for one project does not pick up another project's file", () => {
134+
expect(Registry.names()).toEqual([])
135+
const dirA = project
136+
const other = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), "whl-hint-b-")))
137+
try {
138+
fs.mkdirSync(path.join(other, ".altimate-code"))
139+
fs.writeFileSync(
140+
path.join(other, ".altimate-code", "connections.json"),
141+
JSON.stringify({ "proj-b": { type: "postgres" } }),
142+
)
143+
process.chdir(other)
144+
expect(Registry.names()).toEqual([])
145+
expect(Registry.projectConfigApplies()).toBe(false)
146+
expect(noConnectionsMessage()).not.toContain(Registry.localConfigPath())
147+
expect(noConnectionsMessage()).toContain(Registry.globalConfigPath())
148+
process.chdir(dirA)
149+
expect(Registry.projectConfigApplies()).toBe(true)
150+
// the same project still picks up its own file after an empty answer
151+
fs.mkdirSync(path.join(dirA, ".altimate-code"))
152+
fs.writeFileSync(
153+
path.join(dirA, ".altimate-code", "connections.json"),
154+
JSON.stringify({ "proj-a": { type: "postgres" } }),
155+
)
156+
expect(Registry.names()).toEqual(["proj-a"])
157+
} finally {
158+
process.chdir(dirA)
159+
fs.rmSync(other, { recursive: true, force: true })
160+
}
161+
})
162+
131163
test("setConfigs-injected connections are kept", () => {
132164
Registry.setConfigs({ injected: { type: "postgres" } })
133165
expect(Registry.names()).toEqual(["injected"])

0 commit comments

Comments
 (0)