Skip to content

fix(startup): honor documented env loading - #369

Merged
diegosouzapw merged 4 commits into
cheaper-inference:mainfrom
kfiramar:fix-startup-env-key-loading
Mar 14, 2026
Merged

diegosouzapw merged 4 commits into
cheaper-inference:mainfrom
kfiramar:fix-startup-env-key-loading

Conversation

@kfiramar

Copy link
Copy Markdown
Contributor

Summary

  • align run-next, run-standalone, and Electron with the documented .env lookup order
  • keep Electron secrets rooted in DATA_DIR and pass that same DATA_DIR to the child server
  • refuse to auto-generate a fresh STORAGE_ENCRYPTION_KEY when encrypted credentials already exist
  • add a focused regression test for env precedence and key-safety behavior

Problem

The documented behavior says OmniRoute loads config from ~/.omniroute/.env or ./.env.

In practice, the published CLI wrapper already does that, but the app bootstrap paths used by:

  • npm run start / scripts/run-next.mjs
  • scripts/run-standalone.mjs
  • Electron

were bypassing the CLI wrapper behavior and bootstrapping from server.env plus ./.env instead.

On a machine with an existing encrypted storage.sqlite, that can generate a new STORAGE_ENCRYPTION_KEY in server.env and make previously stored tokens fail to decrypt.

What I Observed

On a local instance using the default DATA_DIR:

  • ~/.omniroute/.env already contained a STORAGE_ENCRYPTION_KEY
  • ~/.omniroute/storage.sqlite already contained encrypted provider credentials (enc:v1:...)
  • starting the app through the repo/app bootstrap path generated a different key in ~/.omniroute/server.env
  • the running app then logged repeated Unsupported state or unable to authenticate data decryption failures

I verified the mismatch directly against a real encrypted row:

  • the key from ~/.omniroute/.env successfully decrypted the stored token
  • the newly generated key from server.env failed on the same token

That is the implementation gap this PR fixes.

Root Cause

The startup paths were inconsistent:

  • bin/omniroute.mjs already prefers DATA_DIR/.env, then ~/.omniroute/.env, then ./.env
  • bootstrapEnv() did not mirror that lookup
  • Electron also maintained its own bootstrap path and did not explicitly pass the resolved DATA_DIR to the child server

This meant different launch modes could resolve different secret sources while still pointing at the same SQLite database.

Fix

  • make scripts/bootstrap-env.mjs use the same preferred .env lookup as bin/omniroute.mjs
  • keep Electron secret loading rooted in the resolved DATA_DIR instead of a divergent path
  • pass DATA_DIR through to the Electron child server process
  • fail loudly instead of minting a new encryption key over an existing encrypted database

Verification

  • added tests/unit/bootstrap-env.test.mjs
  • verified bootstrapEnv() now prefers ~/.omniroute/.env over server.env
  • verified bootstrapEnv() refuses to generate a new key when encrypted credential rows already exist
  • ran node --test tests/unit/bootstrap-env.test.mjs
  • manually verified on a real local data directory that the patched app starts cleanly without the decryption errors seen before

Notes

This PR is intentionally narrow and does not address unrelated runtime issues such as standalone startup warnings or separate schema/migration problems.

Align the app bootstrap paths with the documented CLI env lookup.

The CLI wrapper already loads DATA_DIR/.env, ~/.omniroute/.env, or ./.env,
but run-next, run-standalone, and Electron were bypassing that behavior.
On machines with encrypted credentials, that could generate a fresh
STORAGE_ENCRYPTION_KEY in server.env and make existing tokens unreadable.

This change:
- uses the same preferred .env lookup in bootstrapEnv and Electron
- keeps Electron secrets rooted in DATA_DIR and passes DATA_DIR to the child
- refuses to mint a new encryption key over an existing encrypted database
- adds a focused regression test for env precedence and key safety
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a critical inconsistency in how environment variables, particularly secrets, were loaded across different application startup paths (e.g., npm run start, scripts/run-standalone.mjs, and Electron). Previously, these paths bypassed the documented .env lookup order, leading to situations where a new STORAGE_ENCRYPTION_KEY could be generated, rendering existing encrypted user data inaccessible. The changes ensure that all startup methods adhere to a unified .env loading precedence, prioritize existing keys, and prevent accidental data corruption by refusing to generate new encryption keys when encrypted credentials are already present.

Highlights

  • Consistent Environment Loading: Aligned run-next, run-standalone, and Electron startup processes to honor the documented .env lookup order, ensuring consistent configuration across different application launch methods.
  • Data Directory Management for Electron: Ensured Electron secrets are rooted in the resolved DATA_DIR and that this DATA_DIR is correctly passed to the child server process, preventing divergent secret sources.
  • Encryption Key Safety: Implemented a safeguard to prevent the auto-generation of a new STORAGE_ENCRYPTION_KEY if encrypted credentials already exist in the database, thus avoiding data decryption failures.
  • New Regression Tests: Added a focused regression test suite to verify the correct environment precedence and the key-safety behavior of the bootstrap process.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • electron/main.js
    • Added resolveDataDir, getPreferredEnvFilePath, and hasEncryptedCredentials utility functions to manage data directory resolution, environment file lookup, and encrypted data detection.
    • Modified the secret generation logic within startNextServer to use the resolved DATA_DIR for server.env and updated environment variable merging order to prioritize persisted, then preferredEnv, then process.env.
    • Implemented a check to prevent auto-generation of STORAGE_ENCRYPTION_KEY if encrypted credentials exist, logging an error and stopping startup.
    • Updated fs.mkdirSync to use dataDir instead of userDataDir for consistency.
    • Passed the resolved dataDir to the child server's environment variables.
  • scripts/bootstrap-env.mjs
    • Updated comments to reflect the new .env priority and Electron's use of DATA_DIR.
    • Removed unused createHash import and added createRequire for better-sqlite3.
    • Modified resolveDataDir to accept an env parameter for improved testability.
    • Introduced getPreferredEnvFilePath and hasEncryptedCredentials functions, mirroring those added in electron/main.js.
    • Refactored bootstrapEnv to use the preferred .env lookup and updated the environment variable merging order.
    • Added a check to throw an error if STORAGE_ENCRYPTION_KEY is missing but encrypted credentials are found in the database.
  • tests/unit/bootstrap-env.test.mjs
    • Added a new unit test file for bootstrap-env.mjs.
    • Included a withTempEnv helper function to create isolated test environments.
    • Added a test case to verify that bootstrapEnv correctly prioritizes ~/.omniroute/.env over server.env.
    • Added a test case to confirm that bootstrapEnv throws an error when attempting to generate a new STORAGE_ENCRYPTION_KEY if encrypted data already exists in storage.sqlite.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩

Comment thread scripts/bootstrap-env.mjs Outdated
Comment thread scripts/bootstrap-env.mjs
Comment thread electron/main.js
Comment thread scripts/bootstrap-env.mjs
Comment thread scripts/bootstrap-env.mjs
@kilo-code-bot

kilo-code-bot Bot commented Mar 14, 2026 •

Copy link
Copy Markdown

Code Review Summary

Status: 5 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 4
SUGGESTION 1
Issue Details (click to expand)

WARNING

File Line Issue
scripts/bootstrap-env.mjs 38 Inconsistent path trimming - overridePath?.trim() in bootstrap-env.mjs vs overridePath && overridePath.trim() in electron/main.js. When overridePath is whitespace-only, electron resolves it but bootstrap-env ignores it.
scripts/bootstrap-env.mjs 37 Function signature differs from electron/main.js - both accept (overridePath, env) but with subtle parameter handling differences
electron/main.js 468 Complex env resolution order: resolveDataDir(null, { ...preferredEnv, ...process.env }). The preferredEnv is derived from process.env before merging, which could cause subtle precedence issues.
scripts/bootstrap-env.mjs 135 When dataDirOverride is provided but empty/whitespace, the function still passes it to resolveDataDir. The current logic handles this correctly via optional chaining, but could be confusing.

SUGGESTION

File Line Issue
scripts/bootstrap-env.mjs 67 Function parameter name differs between files - hasEncryptedCredentials(dataDir) in bootstrap-env.mjs vs hasEncryptedCredentials(dbPath) in electron/main.js. Should be consistent.
Other Observations (not in diff)
File Line Issue
tests/unit/bootstrap-env.test.mjs - New test file covers bootstrap-env.mjs but no equivalent tests for electron/main.js code path
Files Reviewed (3 files)
  • electron/main.js - 2 issues
  • scripts/bootstrap-env.mjs - 3 issues
  • tests/unit/bootstrap-env.test.mjs - 0 issues (new test file)

Summary

This PR introduces important improvements to the DATA_DIR resolution and encryption key handling:

  1. Adds resolveDataDir() - Centralizes data directory resolution with proper fallbacks for Windows (APPDATA), Linux (XDG_CONFIG_HOME), and default paths
  2. Adds getPreferredEnvFilePath() - Introduces priority for .env file loading (DATA_DIR/.env → ~/.omniroute/.env → CWD/.env)
  3. Adds hasEncryptedCredentials() - Prevents accidentally overwriting encrypted data by checking for existing encrypted credentials before generating new STORAGE_ENCRYPTION_KEY

The main concerns are around consistency between the Electron and Node.js implementations, and some complex logic in env resolution that could benefit from additional testing.

Recommendation: Address the inconsistencies between the two implementations before merging.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request is a solid improvement that aligns environment variable loading across different startup paths, fixing a critical bug where an incorrect encryption key could be generated, leading to data decryption failures. The changes are well-structured, and the addition of regression tests is excellent. My main feedback is to improve error handling in one of the new helper functions to make it more robust against edge cases.

Comment thread electron/main.js Outdated
Comment thread scripts/bootstrap-env.mjs Outdated
Propagate database inspection failures instead of treating them as
missing encrypted credentials.

This keeps startup from generating a fresh encryption key when an
existing database cannot be inspected and adds a regression test for
that path.
Treat empty or whitespace-only dataDirOverride values as unset so
bootstrapEnv keeps using the normal DATA_DIR and .env lookup path.

Adds a focused regression test for the whitespace override case.
@kfiramar

Copy link
Copy Markdown
Contributor Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request effectively addresses a critical issue with inconsistent environment variable loading across different startup paths, which could lead to problems with data decryption. The changes align the behavior with the documented lookup order and add an important safety check to prevent overwriting encryption keys for existing encrypted databases. The addition of focused regression tests is also a great improvement. My review includes a suggestion to address code duplication between the Electron main process and the bootstrap script to improve long-term maintainability, and also points out a minor bug in one of the new helper functions.

Comment thread electron/main.js
Comment on lines +67 to +123
function resolveDataDir(overridePath, env = process.env) {
if (overridePath && overridePath.trim()) return path.resolve(overridePath);

const configured = env.DATA_DIR?.trim();
if (configured) return path.resolve(configured);

if (process.platform === "win32") {
const appData = env.APPDATA || path.join(require("os").homedir(), "AppData", "Roaming");
return path.join(appData, "omniroute");
}

const xdg = env.XDG_CONFIG_HOME?.trim();
if (xdg) return path.join(path.resolve(xdg), "omniroute");

return path.join(require("os").homedir(), ".omniroute");
}

function getPreferredEnvFilePath(env = process.env) {
const candidates = [];

if (env.DATA_DIR?.trim()) {
candidates.push(path.join(path.resolve(env.DATA_DIR.trim()), ".env"));
}

candidates.push(path.join(resolveDataDir(), ".env"));
candidates.push(path.join(process.cwd(), ".env"));

return candidates.find((filePath) => fs.existsSync(filePath)) || null;
}

function hasEncryptedCredentials(dbPath) {
if (!fs.existsSync(dbPath)) return false;

try {
const Database = require("better-sqlite3");
const db = new Database(dbPath, { readonly: true, fileMustExist: true });
try {
const row = db
.prepare(
`SELECT 1
FROM provider_connections
WHERE access_token LIKE 'enc:v1:%'
OR refresh_token LIKE 'enc:v1:%'
OR api_key LIKE 'enc:v1:%'
OR id_token LIKE 'enc:v1:%'
LIMIT 1`
)
.get();
return !!row;
} finally {
db.close();
}
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`Unable to inspect existing database at ${dbPath}: ${message}`);
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

These helper functions (resolveDataDir, getPreferredEnvFilePath, hasEncryptedCredentials) are duplicated from scripts/bootstrap-env.mjs. While I understand this is due to the CJS/ESM module differences between Electron's main process and the scripts, this duplication creates a maintenance burden. If the logic changes in one place, it must be manually updated in the other.

To avoid this, consider extracting these shared functions into a separate CommonJS file (e.g., scripts/env-helpers.cjs). This new file could be require'd here and also imported into the ESM bootstrap-env.mjs module. This would centralize the logic and improve maintainability.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping this PR scoped to the startup/key-safety fix. I agree the duplication is a maintenance cost, but extracting a shared helper across the Electron CJS path and the bootstrap ESM path would broaden the runtime/module-loading surface for this PR. I would prefer to handle that as a separate follow-up refactor once the startup regression is merged.

Comment thread scripts/bootstrap-env.mjs Outdated
Comment thread electron/main.js Outdated
Keep getPreferredEnvFilePath consistent with its env parameter by
passing that env through resolveDataDir in both bootstrap and Electron.

This avoids silently falling back to process.env when a custom env map
is supplied.
@kfiramar

Copy link
Copy Markdown
Contributor Author

Damn, those code reviews are legit

@kfiramar

Copy link
Copy Markdown
Contributor Author

I've found a potential issue, please do not merge yet

@kfiramar

kfiramar commented Mar 14, 2026 •

Copy link
Copy Markdown
Contributor Author

Those issues are not related (I was facing 3 separate issues)

Opening PRs soon for them (Already opened)

@diegosouzapw
diegosouzapw merged commit 66f0a8f into cheaper-inference:main Mar 14, 2026
diegosouzapw added a commit that referenced this pull request Mar 14, 2026
bug fixes (PRs #373, #371, #372, #369 by @kfiramar):
- fix(db): provider_connections.group column migration for existing DBs
- fix(i18n): replace missing deleteConnection key with delete in tooltip
- fix(auth): clear stale error metadata on genuine provider recovery
- fix(startup): unify env loading across npm/electron startup paths

code quality improvements (per kilo-code-bot review):
- docs: document result.success vs response.ok patterns in auth.ts
- refactor: normalize overridePath?.trim() in electron/main.js
- docs: explain preferredEnv merge order intent
prakersh pushed a commit to prakersh/OmniRoute that referenced this pull request Mar 26, 2026
…v-key-loading

Thanks @kfiramar! 🎉 Critical security fix — different startup paths were generating different `STORAGE_ENCRYPTION_KEY` values over the same SQLite database, causing `Unsupported state or unable to authenticate data` for all stored tokens.

Improvements added on top:
- Normalized `overridePath?.trim()` in `electron/main.js` to match `bootstrap-env.mjs` (addresses kilo-code-bot warning cheaper-inference#1)
- Added explanatory comment documenting the `preferredEnv` merge order intent in Electron startup (addresses kilo-code-bot warning cheaper-inference#3)

4 commits + 113-line test file. The fail-closed behaviour (refusing to mint a new key when encrypted rows exist) is an excellent safeguard. Merged!
prakersh pushed a commit to prakersh/OmniRoute that referenced this pull request Mar 26, 2026
bug fixes (PRs cheaper-inference#373, cheaper-inference#371, cheaper-inference#372, cheaper-inference#369 by @kfiramar):
- fix(db): provider_connections.group column migration for existing DBs
- fix(i18n): replace missing deleteConnection key with delete in tooltip
- fix(auth): clear stale error metadata on genuine provider recovery
- fix(startup): unify env loading across npm/electron startup paths

code quality improvements (per kilo-code-bot review):
- docs: document result.success vs response.ok patterns in auth.ts
- refactor: normalize overridePath?.trim() in electron/main.js
- docs: explain preferredEnv merge order intent
Poid-ZA pushed a commit to Poid-ZA/OmniRoute that referenced this pull request Aug 5, 2026
…v-key-loading

Thanks @kfiramar! 🎉 Critical security fix — different startup paths were generating different `STORAGE_ENCRYPTION_KEY` values over the same SQLite database, causing `Unsupported state or unable to authenticate data` for all stored tokens.

Improvements added on top:
- Normalized `overridePath?.trim()` in `electron/main.js` to match `bootstrap-env.mjs` (addresses kilo-code-bot warning cheaper-inference#1)
- Added explanatory comment documenting the `preferredEnv` merge order intent in Electron startup (addresses kilo-code-bot warning cheaper-inference#3)

4 commits + 113-line test file. The fail-closed behaviour (refusing to mint a new key when encrypted rows exist) is an excellent safeguard. Merged!
Poid-ZA pushed a commit to Poid-ZA/OmniRoute that referenced this pull request Aug 5, 2026
bug fixes (PRs cheaper-inference#373, cheaper-inference#371, cheaper-inference#372, cheaper-inference#369 by @kfiramar):
- fix(db): provider_connections.group column migration for existing DBs
- fix(i18n): replace missing deleteConnection key with delete in tooltip
- fix(auth): clear stale error metadata on genuine provider recovery
- fix(startup): unify env loading across npm/electron startup paths

code quality improvements (per kilo-code-bot review):
- docs: document result.success vs response.ok patterns in auth.ts
- refactor: normalize overridePath?.trim() in electron/main.js
- docs: explain preferredEnv merge order intent
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…v-key-loading

Thanks @kfiramar! 🎉 Critical security fix — different startup paths were generating different `STORAGE_ENCRYPTION_KEY` values over the same SQLite database, causing `Unsupported state or unable to authenticate data` for all stored tokens.

Improvements added on top:
- Normalized `overridePath?.trim()` in `electron/main.js` to match `bootstrap-env.mjs` (addresses kilo-code-bot warning cheaper-inference#1)
- Added explanatory comment documenting the `preferredEnv` merge order intent in Electron startup (addresses kilo-code-bot warning cheaper-inference#3)

4 commits + 113-line test file. The fail-closed behaviour (refusing to mint a new key when encrypted rows exist) is an excellent safeguard. Merged!
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
bug fixes (PRs cheaper-inference#373, cheaper-inference#371, cheaper-inference#372, cheaper-inference#369 by @kfiramar):
- fix(db): provider_connections.group column migration for existing DBs
- fix(i18n): replace missing deleteConnection key with delete in tooltip
- fix(auth): clear stale error metadata on genuine provider recovery
- fix(startup): unify env loading across npm/electron startup paths

code quality improvements (per kilo-code-bot review):
- docs: document result.success vs response.ok patterns in auth.ts
- refactor: normalize overridePath?.trim() in electron/main.js
- docs: explain preferredEnv merge order intent
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants