Latest Results
fix(sync): skip lone mismatched .env.<env> in auto-detect (#407)
Fixes #395: a mapping configured for env X with no `.env.X` but exactly
one other `.env.Y` fell through to `detect_env_file`, which adopted Y's
suffix → synced `.env.Y` under `DOTENV_PRIVATE_KEY_Y`. Fix: only adopt a
lone `.env.<suffix>` when the suffix matches the requested environment;
otherwise return not_found so sync/vault-push SKIPS. Load-bearing
regression test added.
Closes #395
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
This PR fixes issue #395 where a sync mapping configured for environment
X (e.g. `production`) with no `.env.X` but a single `.env.Y` (e.g.
`.env.staging`) would fall through to `detect_env_file`, which adopted
Y's suffix and synced the staging file under
`DOTENV_PRIVATE_KEY_STAGING` for a production mapping. The fix adds a
suffix-match guard (`_resolve_lone_env_file`) so a lone `.env.*` is only
adopted when its suffix equals the requested environment; otherwise
`"not_found"` is returned and the mapping is skipped.
- **`env_files.py`**: Introduces `_resolve_lone_env_file`,
`_candidate_env_files`, `_one_or_ambiguous`, and
`_resolve_explicit_env_file` as private helpers; refactors
`detect_env_file` and `resolve_mapping_env_file` to use them,
eliminating the duplicate exclusion-filter logic and the unsafe
lone-file adoption.
- **Test suite**: The test previously validating the buggy behavior
(`environment=\"production\"` + `.env.staging`) is corrected to use
`environment=\"staging\"`, and a new load-bearing regression test is
added across three test files (`test_env_files.py`,
`test_cov_sync_engine.py`, `test_cli_vault_push.py`).
<h3>Confidence Score: 5/5</h3>
Safe to merge — the change narrows a single detection fallback path, all
affected callers are covered by existing and new tests, and no
pre-existing correct behavior is altered.
The fix is surgical and well-contained: only the lone-file adoption
branch in detect_env_file changes, and every other resolution path
(exact match, custom-named files, explicit env_file, plain .env) is
untouched. Regression tests were added at the unit, integration, and
end-to-end CLI levels, and the previously incorrect test that was
validating the buggy behavior has been corrected. The refactoring
helpers are straightforward extractions with no logic change.
No files require special attention.
<h3>Important Files Changed</h3>
| Filename | Overview |
|----------|----------|
| src/envdrift/env_files.py | Core fix: adds _resolve_lone_env_file with
a suffix-equality guard, extracts
_candidate_env_files/_one_or_ambiguous/_resolve_explicit_env_file as
clean helpers. Logic is correct and behavior-equivalent for all
pre-existing valid cases. |
| tests/unit/coverage/test_cov_sync_engine.py | Class docstring and
inline comment updated per prior review thread; new
test_lone_other_env_file_is_skipped_not_synced is the key end-to-end
regression test for #395. |
| tests/unit/test_cli_vault_push.py | Extracts
_setup_push_all_single_mapping helper; refactors the old buggy-behavior
test to use environment='staging'; adds
test_push_all_skips_lone_other_environment_file regression for #395. |
| tests/unit/test_env_files.py | Fixes
test_resolve_mapping_env_file_preserves_legacy_single_env_detection
(environment now 'sqa' to match the file); adds
test_resolve_mapping_env_file_skips_lone_other_environment_file
regression. |
| tests/unit/test_cli.py | Adds two direct unit tests for
detect_env_file: mismatched-env returns not_found; matching-env returns
found. Good coverage of the new guard in isolation. |
| docs/guides/env-file-sync.md | Documentation updated to accurately
describe the new restriction: only a .env.<env> whose suffix matches the
mapping's environment is auto-adopted. |
</details>
<h3>Flowchart</h3>
```mermaid
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[resolve_mapping_env_file] --> B{env_file configured?}
B -- yes --> C[_resolve_explicit_env_file\nfolder_not_found / found / not_found]
B -- no --> D{.env.effective_env\nexists?}
D -- yes --> E[Return found\nexact match]
D -- no --> F[_match_env_files_for_environment\ncustom-named files]
F --> G{matches?}
G -- 1 file --> H[Return found]
G -- >1 files --> I[Return multiple_found]
G -- none --> J[detect_env_file\nlegacy fallback]
J --> K{plain .env\nexists?}
K -- yes --> L[Return found\ndefault env]
K -- no --> M{lone .env.* file?}
M -- yes --> N{suffix ==\nrequested env?}
N -- yes --> O[Return found]
N -- no --> P[Return not_found\n⚠️ FIX for #395: skip]
M -- ">1" --> Q[Return multiple_found]
M -- none --> R[Return not_found]
```
<sub>Reviews (6): Last reviewed commit: ["test(vault-push): share --all
scaffold
t..."](https://github.com/jainal09/envdrift/commit/4087a383a87a86df7fadb91f66e170fd9cd10f53)
| [Re-trigger
Greptile](https://app.greptile.com/api/retrigger?id=36051861)</sub>
<!-- /greptile_comment -->
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Latest Branches
+2%
release-please--branches--main--components--vscode +2%
release-please--branches--main--components--envdrift 0%
release-please--branches--main--components--agent © 2026 CodSpeed Technology