fix(opencode): Use opencode.jsonc when present (#1590)

## Description

Fix OpenCode proxy injection so it respects user configurations that use
the `.jsonc` extension, preventing Headroom from creating a duplicate
`.json` file that overrides it.

Closes #1588

## Type of Change

- [x] Bug fix (non-breaking change that fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Performance improvement
- [ ] Code refactoring (no functional changes)

## Changes Made

- Updated `opencode_config_path` in `paths.py` to check for `.jsonc`
- Updated backup creation in `config.py` to preserve the original
extension

## Testing

<!-- Check what you actually ran, then paste the real command output
below. -->

- [ ] Unit tests pass (`pytest`)
- [ ] Linting passes (`ruff check .`)
- [ ] Type checking passes (`mypy headroom`)
- [ ] New tests added for new functionality
- [x] Manual testing performed

### Test Output

```text
N/A
```

## Real Behavior Proof

- Environment: local headroom dev
- Exact command / steps: creating a dummy
`.config/opencode/opencode.jsonc` and running `headroom wrap opencode`.
- Observed result: Headroom successfully injects into `.jsonc` and
creates a backup named `opencode.jsonc.headroom-backup`.
- Not tested: N/A

## Review Readiness

- [x] I have performed a self-review
- [x] This PR is ready for human review

## Checklist

- [x] My code follows the project's style guidelines
- [x] I have performed a self-review of my code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable

## Screenshots (if applicable)

## Additional Notes
This commit is contained in:
Tanmay Garg 2026-07-17 02:21:21 +05:30 committed by GitHub
parent f42ce4a239
commit 4e2bbfee3f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 69 additions and 4 deletions

View file

@ -125,13 +125,20 @@ def opencode_config_path() -> Path:
"""Return the OpenCode config path.
Resolves ``~/.config/opencode/opencode.json`` when ``OPENCODE_CONFIG``
is unset; otherwise the value of that environment variable.
is unset; otherwise the value of that environment variable. Checks for
``opencode.jsonc`` as well.
"""
env_path = os.environ.get("OPENCODE_CONFIG", "").strip()
if env_path:
return Path(env_path).expanduser()
return Path.home() / ".config" / "opencode" / "opencode.json"
base_dir = Path.home() / ".config" / "opencode"
jsonc_path = base_dir / "opencode.jsonc"
if jsonc_path.exists():
return jsonc_path
return base_dir / "opencode.json"
def zcode_config_dir() -> Path:

View file

@ -81,7 +81,7 @@ def _opencode_home_dir() -> Path:
def opencode_config_paths() -> tuple[Path, Path]:
"""Return ``(config_file, backup_file)`` for OpenCode."""
config_file = opencode_config_path()
backup_file = config_file.with_suffix(".json.headroom-backup")
backup_file = config_file.with_name(config_file.name + ".headroom-backup")
return config_file, backup_file

View file

@ -346,7 +346,7 @@ def test_unwrap_opencode_restores_from_backup(
_set_test_home(monkeypatch, tmp_path)
config_file = tmp_path / ".config" / "opencode" / "opencode.json"
backup_file = config_file.with_suffix(".json.headroom-backup")
backup_file = config_file.with_name("opencode.json.headroom-backup")
config_file.parent.mkdir(parents=True, exist_ok=True)
original = '{"model": "openai/gpt-4o"}'
config_file.write_text(original)
@ -361,6 +361,31 @@ def test_unwrap_opencode_restores_from_backup(
assert config_file.read_text(encoding="utf-8") == original
def test_unwrap_opencode_restores_from_backup_jsonc(
runner: CliRunner,
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Unwrap restores the pre-wrap backup and removes it for jsonc files."""
monkeypatch.chdir(tmp_path)
_set_test_home(monkeypatch, tmp_path)
config_file = tmp_path / ".config" / "opencode" / "opencode.jsonc"
backup_file = config_file.with_name("opencode.jsonc.headroom-backup")
config_file.parent.mkdir(parents=True, exist_ok=True)
original = '{\n // User comment\n "model": "openai/gpt-4o"\n}'
config_file.write_text(original)
backup_file.write_text(original)
with patch.object(wrap_mod, "_stop_local_proxy_for_unwrap", return_value="stopped"):
result = runner.invoke(main, ["unwrap", "opencode"])
assert result.exit_code == 0, result.output
assert "Restored prior" in result.output
assert not backup_file.exists()
assert config_file.read_text(encoding="utf-8") == original
def test_unwrap_opencode_strips_blocks_when_no_backup(
runner: CliRunner,
tmp_path: Path,

View file

@ -49,6 +49,39 @@ def test_opencode_config_paths_from_env(tmp_path: Path, monkeypatch: pytest.Monk
assert backup_file == tmp_path / "custom" / "opencode.json.headroom-backup"
def test_opencode_config_paths_default_jsonc(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Default config path resolves to opencode.jsonc when it exists."""
_set_test_home(monkeypatch, tmp_path)
base_dir = tmp_path / ".config" / "opencode"
base_dir.mkdir(parents=True, exist_ok=True)
jsonc_path = base_dir / "opencode.jsonc"
jsonc_path.write_text("{}")
config_file, backup_file = opencode_config_paths()
assert config_file == jsonc_path
assert backup_file == base_dir / "opencode.jsonc.headroom-backup"
def test_opencode_config_paths_env_overrides_jsonc(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""OPENCODE_CONFIG takes precedence even if opencode.jsonc exists."""
_set_test_home(monkeypatch, tmp_path)
base_dir = tmp_path / ".config" / "opencode"
base_dir.mkdir(parents=True, exist_ok=True)
jsonc_path = base_dir / "opencode.jsonc"
jsonc_path.write_text("{}")
custom_path = tmp_path / "custom" / "opencode.json"
monkeypatch.setenv("OPENCODE_CONFIG", str(custom_path))
config_file, backup_file = opencode_config_paths()
assert config_file == custom_path
assert backup_file == tmp_path / "custom" / "opencode.json.headroom-backup"
# ---------------------------------------------------------------------------
# Snapshot
# ---------------------------------------------------------------------------