You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
PortOS reads and rewrites a single key in a .env file in four places, each with its own parser and its own insert policy:
Where
Read
Write
Insert position
scripts/lib/envFile.js
parseEnvFile
upsertEnvKey
prepend
server/services/localLlm.js
readEnv (private)
writeBackend (private)
prepend
server/lib/vaultCrypto.js
inline
inline
—
server/lib/vllmQwenProvision.js
parseEnvContents
mergeEnvFileContents / upsertEnvLine
append
They already disagree in ways that matter. scripts/lib/envFile.js uses a replacer function so a value containing $& / $` / $' is written literally rather than expanded by String.replace — a bug it hit on a password with a $ in it. That fix was independently re-lost and re-found in vllmQwenProvision.js#upsertEnvLine. Only two of the four skip # comments. localLlm.js also lets .envwin overprocess.env while vllmQwenProject.js#resolveVllmProjectDir deliberately inverts that; both document their reasoning, but two "record a marker in .env" features with opposite precedence is a coin-flip for the next one.
Three of the four also anchor the path differently: PATHS.root (localLlm.js, ecosystem.config.cjs) versus PATHS.installRoot (vllmQwenProject.js#PORTOS_ENV_PATH). Only the second is correct for a server booted from a CoS agent worktree, where the checkout has no .env and the install does (#1947).
Decision (already made — do not re-litigate)
One helper, in server/lib/, and every server-side caller collapses onto it. scripts/lib/envFile.jsstays as it is — its header states it must have zero dependencies and must not import from server/lib, because it runs before/around npm install. Two implementations across that boundary is the correct number; four is not.
Move upsertEnvLine / parseEnvContents reuse out of vllmQwenProvision.js into it, leaving mergeEnvFileContents (additive-by-contract, for the project's.env) where it is.
Collapse vllmQwenProject.js's readRecordedVllmProjectDir / recordVllmProjectDir and localLlm.js's readEnv / writeBackend / ENV_PATH onto it.
Settle the precedence question once, in that module's docstring: an exported process.env value is this run's decision and wins; the .env record is durable memory and loses. Adjust localLlm.js#getBackend to match, and keep its "validate each source before falling through" guard (a stale/invalid marker must not mask a valid override) — that part is orthogonal and correct.
server/lib/vaultCrypto.js's inline pair goes too, unless its bootstrapping order makes an import impossible — check before assuming, and say so in the PR if it does.
Acceptance
One server-side .env key reader and one writer; grep -n "readFileSync.*\.env" server/ finds only that module.
localLlm.js's existing suite passes unchanged (it mocks PATHS through ../lib/fileUtils.js, so the new module must resolve its default path lazily or accept the path as a parameter — the envPath parameter already used by vllmQwenProject.js is the pattern).
A value containing $& round-trips literally, with a test naming that.
Precedence is stated once and both callers follow it.
Context
Follow-up from the vLLM Windows placement change; raised by both the reuse and altitude passes on that PR. vllmQwenProject.js already reuses parseEnvContents and the new upsertEnvLine rather than adding a fifth copy — this issue is about the three that predate it.
Problem
PortOS reads and rewrites a single key in a
.envfile in four places, each with its own parser and its own insert policy:scripts/lib/envFile.jsparseEnvFileupsertEnvKeyserver/services/localLlm.jsreadEnv(private)writeBackend(private)server/lib/vaultCrypto.jsserver/lib/vllmQwenProvision.jsparseEnvContentsmergeEnvFileContents/upsertEnvLineThey already disagree in ways that matter.
scripts/lib/envFile.jsuses a replacer function so a value containing$&/$`/$'is written literally rather than expanded byString.replace— a bug it hit on a password with a$in it. That fix was independently re-lost and re-found invllmQwenProvision.js#upsertEnvLine. Only two of the four skip#comments.localLlm.jsalso lets.envwin overprocess.envwhilevllmQwenProject.js#resolveVllmProjectDirdeliberately inverts that; both document their reasoning, but two "record a marker in.env" features with opposite precedence is a coin-flip for the next one.Three of the four also anchor the path differently:
PATHS.root(localLlm.js,ecosystem.config.cjs) versusPATHS.installRoot(vllmQwenProject.js#PORTOS_ENV_PATH). Only the second is correct for a server booted from a CoS agent worktree, where the checkout has no.envand the install does (#1947).Decision (already made — do not re-litigate)
One helper, in
server/lib/, and every server-side caller collapses onto it.scripts/lib/envFile.jsstays as it is — its header states it must have zero dependencies and must not import fromserver/lib, because it runs before/aroundnpm install. Two implementations across that boundary is the correct number; four is not.server/lib/portosEnv.js:PORTOS_ENV_PATH(anchored toPATHS.installRoot, with the Boot migrations resolve data root from executing-file location — crash when run from a CoS agent worktree #1947 reasoning in the docstring),readPortosEnvValue(key, envPath?),upsertPortosEnvLine(key, value, envPath?)(async, overatomicWrite, replacer-function). Barrel + README row per the Module Organization rule.upsertEnvLine/parseEnvContentsreuse out ofvllmQwenProvision.jsinto it, leavingmergeEnvFileContents(additive-by-contract, for the project's.env) where it is.vllmQwenProject.js'sreadRecordedVllmProjectDir/recordVllmProjectDirandlocalLlm.js'sreadEnv/writeBackend/ENV_PATHonto it.process.envvalue is this run's decision and wins; the.envrecord is durable memory and loses. AdjustlocalLlm.js#getBackendto match, and keep its "validate each source before falling through" guard (a stale/invalid marker must not mask a valid override) — that part is orthogonal and correct.server/lib/vaultCrypto.js's inline pair goes too, unless its bootstrapping order makes an import impossible — check before assuming, and say so in the PR if it does.Acceptance
.envkey reader and one writer;grep -n "readFileSync.*\.env" server/finds only that module.localLlm.js's existing suite passes unchanged (it mocksPATHSthrough../lib/fileUtils.js, so the new module must resolve its default path lazily or accept the path as a parameter — theenvPathparameter already used byvllmQwenProject.jsis the pattern).$&round-trips literally, with a test naming that.Context
Follow-up from the vLLM Windows placement change; raised by both the reuse and altitude passes on that PR.
vllmQwenProject.jsalready reusesparseEnvContentsand the newupsertEnvLinerather than adding a fifth copy — this issue is about the three that predate it.