Modify get_config_path() to check NANOBOT_CONFIG env var first before
falling back to ~/.nanobot/config.json. This allows staging/custom
setups to use a different config file without modifying code.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Added logic to _migrate_config() to automatically populate the api_key field
from oauthCredentials.access_token when present. This allows configs that
store OAuth tokens in the oauthCredentials structure to work correctly.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Creates test-pr.sh to streamline PR testing workflow:
- Fetches PR from wylab remote
- Checks out PR branch
- Installs in editable mode with uv
- Runs test with staging config
- Uses NANOBOT_CONFIG to isolate from production
Usage: ./test-pr.sh <pr-number> [test-message]
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
PR Review: Add local staging environment for PR testing
Overview
Clean, focused PR with three logical changes:
NANOBOT_CONFIG env var for custom config path
OAuth credentials migration in _migrate_config
test-pr.sh helper script
Findings
1. NANOBOT_CONFIG env var support — looks good
The implementation is clean and minimal. Both load_config and save_config use get_config_path() so they stay consistent. One note: _get_oauth_store_dir() still hardcodes Path.home() / ".nanobot" regardless of NANOBOT_CONFIG. This means OAuth creds are always read from ~/.nanobot/ even with a custom config path. The PR description says "shared OAuth with production" so this seems intentional, but it's an implicit coupling worth being aware of.
2. OAuth credentials migration — functional, but has a gap
The migration extracts access_token from oauthCredentials into api_key, but leaves the oauthCredentials dict in place. After migration, the token exists in two locations in the config dict. Consider cleaning up the source:
ifaccess_tokenandnotprovider_config.get("api_key"):provider_config["api_key"]=access_tokendelprovider_config["oauthCredentials"]# clean up migrated data
Also, provider_name is unused in the loop — should be _ per convention.
3. test-pr.sh — works but has rough edges
Issue
Severity
Detail
Branch collision
Medium
git fetch wylab "pull/$PR_NUM/head:pr-$PR_NUM" will fail if pr-$PR_NUM already exists from a previous test. Add git branch -D "pr-$PR_NUM" 2>/dev/null before fetch, or use + refspec (+pull/$PR_NUM/head:pr-$PR_NUM)
Hardcoded paths
Low
/config/workspace/... — not portable, but clearly a dev-only tool
No cleanup
Low
Branches accumulate over time
4. No tests added
Neither the env var support nor the migration logic have test coverage. _migrate_config has zero test coverage in the existing test suite. At minimum:
Test get_config_path() with/without NANOBOT_CONFIG set
Test _migrate_config with oauthCredentials present/absent/already-migrated
Summary
Aspect
Verdict
Correctness
✅ Code does what it says
Security
✅ No new attack surface
Tests
⚠️ Migration logic untested
Script robustness
⚠️ Branch collision on re-run
Recommendation: Mergeable as-is for a dev tooling PR, but the branch collision in test-pr.sh will bite on second use. The missing tests are a risk for the migration logic in loader.py.
🤖 Review by Claude Code
## PR Review: Add local staging environment for PR testing
### Overview
Clean, focused PR with three logical changes:
1. `NANOBOT_CONFIG` env var for custom config path
2. OAuth credentials migration in `_migrate_config`
3. `test-pr.sh` helper script
---
### Findings
**1. `NANOBOT_CONFIG` env var support — looks good**
The implementation is clean and minimal. Both `load_config` and `save_config` use `get_config_path()` so they stay consistent. One note: `_get_oauth_store_dir()` still hardcodes `Path.home() / ".nanobot"` regardless of `NANOBOT_CONFIG`. This means OAuth creds are always read from `~/.nanobot/` even with a custom config path. The PR description says "shared OAuth with production" so this seems intentional, but it's an implicit coupling worth being aware of.
**2. OAuth credentials migration — functional, but has a gap**
The migration extracts `access_token` from `oauthCredentials` into `api_key`, but leaves the `oauthCredentials` dict in place. After migration, the token exists in two locations in the config dict. Consider cleaning up the source:
```python
if access_token and not provider_config.get("api_key"):
provider_config["api_key"] = access_token
del provider_config["oauthCredentials"] # clean up migrated data
```
Also, `provider_name` is unused in the loop — should be `_` per convention.
**3. `test-pr.sh` — works but has rough edges**
| Issue | Severity | Detail |
|-------|----------|--------|
| Branch collision | Medium | `git fetch wylab "pull/$PR_NUM/head:pr-$PR_NUM"` will fail if `pr-$PR_NUM` already exists from a previous test. Add `git branch -D "pr-$PR_NUM" 2>/dev/null` before fetch, or use `+` refspec (`+pull/$PR_NUM/head:pr-$PR_NUM`) |
| Hardcoded paths | Low | `/config/workspace/...` — not portable, but clearly a dev-only tool |
| No cleanup | Low | Branches accumulate over time |
**4. No tests added**
Neither the env var support nor the migration logic have test coverage. `_migrate_config` has zero test coverage in the existing test suite. At minimum:
- Test `get_config_path()` with/without `NANOBOT_CONFIG` set
- Test `_migrate_config` with `oauthCredentials` present/absent/already-migrated
---
### Summary
| Aspect | Verdict |
|--------|---------|
| Correctness | ✅ Code does what it says |
| Security | ✅ No new attack surface |
| Tests | ⚠️ Migration logic untested |
| Script robustness | ⚠️ Branch collision on re-run |
**Recommendation:** Mergeable as-is for a dev tooling PR, but the branch collision in `test-pr.sh` will bite on second use. The missing tests are a risk for the migration logic in `loader.py`.
🤖 Review by Claude Code
Use + refspec to force update pr-N branch on re-run. Prevents
'already exists' error when testing the same PR multiple times.
Addresses review feedback from PR #32.
- Remove oauthCredentials dict after extracting api_key to avoid duplication
- Use _ for unused provider_name variable per convention
Addresses review feedback from PR #32.
The previous review identified 4 issues. All have been addressed in follow-up commits:
Original Issue
Fix Commit
Status
oauthCredentials left after migration
153b08f — del provider_config["oauthCredentials"]
✅ Fixed
provider_name unused variable
153b08f — changed to _
✅ Fixed
Branch collision in test-pr.sh
1b920d7 — + refspec for force-update
✅ Fixed
No test coverage
a3fe901 — 7 tests added
✅ Fixed
Remaining Findings
1. Edge case in migration: oauthCredentials persists when api_key already set
When both api_key and oauthCredentials exist (the "already migrated" case), oauthCredentials is intentionally kept (line 104: migration only fires when api_key is absent). This means stale oauthCredentials data lingers in the config dict.
If the Pydantic schema ever switches to extra='forbid', this leftover field would cause a validation error. Currently harmless, but worth noting. Consider always cleaning up oauthCredentials regardless:
ifaccess_tokenandnotprovider_config.get("api_key"):provider_config["api_key"]=access_token# Always clean up oauthCredentials regardless of migrationif"oauthCredentials"inprovider_config:delprovider_config["oauthCredentials"]
Severity: Low — current schema allows extra fields.
2. Test env var isolation uses manual try/finally instead of monkeypatch
Tests manipulate os.environ directly with try/finally cleanup. Using pytest's monkeypatch fixture would be more idiomatic and automatically cleaned up:
Severity: Minor — current approach works, just less idiomatic.
3. save_config writes migrated-away fields back
save_config calls config.model_dump(by_alias=True) which serializes the Pydantic model — this is fine. But if a config was loaded (triggering migration), then saved, the oauthCredentials field is gone from the on-disk config permanently. This is the correct behavior (migration is intentionally one-way), but worth being explicit that running nanobot with NANOBOT_CONFIG pointing to a production config will permanently modify it on save. The staging setup avoids this by using a separate config file.
Severity: Informational — by design, just worth documenting.
Verified
✅ All 7 new tests pass locally
✅NANOBOT_CONFIG respected by both load_config and save_config via shared get_config_path()
✅_get_oauth_store_dir() intentionally hardcoded to ~/.nanobot — staging shares production OAuth creds
✅test-pr.sh+ refspec handles re-runs correctly
✅ Migration cleans up oauthCredentials after extraction
✅ Migration preserves api_key when already set (no overwrite)
✅ No security concerns — env var only changes config file path
Verdict
Ready to merge. All review feedback addressed. Remaining items are minor/informational and don't block.
🤖 Review by Claude Code
## PR Review (Round 2): Post-feedback assessment
The previous review identified 4 issues. All have been addressed in follow-up commits:
| Original Issue | Fix Commit | Status |
|---|---|---|
| `oauthCredentials` left after migration | `153b08f` — `del provider_config["oauthCredentials"]` | ✅ Fixed |
| `provider_name` unused variable | `153b08f` — changed to `_` | ✅ Fixed |
| Branch collision in `test-pr.sh` | `1b920d7` — `+` refspec for force-update | ✅ Fixed |
| No test coverage | `a3fe901` — 7 tests added | ✅ Fixed |
---
### Remaining Findings
**1. Edge case in migration: `oauthCredentials` persists when `api_key` already set**
When both `api_key` and `oauthCredentials` exist (the "already migrated" case), `oauthCredentials` is intentionally kept (line 104: migration only fires when `api_key` is absent). This means stale `oauthCredentials` data lingers in the config dict.
If the Pydantic schema ever switches to `extra='forbid'`, this leftover field would cause a validation error. Currently harmless, but worth noting. Consider always cleaning up `oauthCredentials` regardless:
```python
if access_token and not provider_config.get("api_key"):
provider_config["api_key"] = access_token
# Always clean up oauthCredentials regardless of migration
if "oauthCredentials" in provider_config:
del provider_config["oauthCredentials"]
```
**Severity: Low** — current schema allows extra fields.
**2. Test env var isolation uses manual try/finally instead of `monkeypatch`**
Tests manipulate `os.environ` directly with try/finally cleanup. Using pytest's `monkeypatch` fixture would be more idiomatic and automatically cleaned up:
```python
def test_get_config_path_with_env_var(monkeypatch):
monkeypatch.setenv("NANOBOT_CONFIG", "/tmp/test-nanobot-config.json")
assert get_config_path() == Path("/tmp/test-nanobot-config.json")
```
**Severity: Minor** — current approach works, just less idiomatic.
**3. `save_config` writes migrated-away fields back**
`save_config` calls `config.model_dump(by_alias=True)` which serializes the Pydantic model — this is fine. But if a config was loaded (triggering migration), then saved, the `oauthCredentials` field is gone from the on-disk config permanently. This is the correct behavior (migration is intentionally one-way), but worth being explicit that running nanobot with `NANOBOT_CONFIG` pointing to a production config will permanently modify it on save. The staging setup avoids this by using a separate config file.
**Severity: Informational** — by design, just worth documenting.
---
### Verified
- ✅ All 7 new tests pass locally
- ✅ `NANOBOT_CONFIG` respected by both `load_config` and `save_config` via shared `get_config_path()`
- ✅ `_get_oauth_store_dir()` intentionally hardcoded to `~/.nanobot` — staging shares production OAuth creds
- ✅ `test-pr.sh` `+` refspec handles re-runs correctly
- ✅ Migration cleans up `oauthCredentials` after extraction
- ✅ Migration preserves `api_key` when already set (no overwrite)
- ✅ No security concerns — env var only changes config file path
### Verdict
**Ready to merge.** All review feedback addressed. Remaining items are minor/informational and don't block.
🤖 Review by Claude Code
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Local staging environment on code-server for testing PRs before merge. Enables:
./test-pr.sh <pr-number>Changes
Infrastructure
NANOBOT_CONFIGenvironment variable support — specify custom config file pathapi_keyfromoauthCredentials.access_tokenstructuretest-pr.shhelper script — automate PR checkout, install, and testStaging Setup
/config/workspace/.nanobot-staging/{workspace}/sessions/)Testing
All components tested:
Usage
Files Changed
nanobot/config/loader.py— NANOBOT_CONFIG support + OAuth migrationtest-pr.sh— PR testing automation script🤖 Generated via staging environment implementation plan
PR Review: Add local staging environment for PR testing
Overview
Clean, focused PR with three logical changes:
NANOBOT_CONFIGenv var for custom config path_migrate_configtest-pr.shhelper scriptFindings
1.
NANOBOT_CONFIGenv var support — looks goodThe implementation is clean and minimal. Both
load_configandsave_configuseget_config_path()so they stay consistent. One note:_get_oauth_store_dir()still hardcodesPath.home() / ".nanobot"regardless ofNANOBOT_CONFIG. This means OAuth creds are always read from~/.nanobot/even with a custom config path. The PR description says "shared OAuth with production" so this seems intentional, but it's an implicit coupling worth being aware of.2. OAuth credentials migration — functional, but has a gap
The migration extracts
access_tokenfromoauthCredentialsintoapi_key, but leaves theoauthCredentialsdict in place. After migration, the token exists in two locations in the config dict. Consider cleaning up the source:Also,
provider_nameis unused in the loop — should be_per convention.3.
test-pr.sh— works but has rough edgesgit fetch wylab "pull/$PR_NUM/head:pr-$PR_NUM"will fail ifpr-$PR_NUMalready exists from a previous test. Addgit branch -D "pr-$PR_NUM" 2>/dev/nullbefore fetch, or use+refspec (+pull/$PR_NUM/head:pr-$PR_NUM)/config/workspace/...— not portable, but clearly a dev-only tool4. No tests added
Neither the env var support nor the migration logic have test coverage.
_migrate_confighas zero test coverage in the existing test suite. At minimum:get_config_path()with/withoutNANOBOT_CONFIGset_migrate_configwithoauthCredentialspresent/absent/already-migratedSummary
Recommendation: Mergeable as-is for a dev tooling PR, but the branch collision in
test-pr.shwill bite on second use. The missing tests are a risk for the migration logic inloader.py.🤖 Review by Claude Code
PR Review (Round 2): Post-feedback assessment
The previous review identified 4 issues. All have been addressed in follow-up commits:
oauthCredentialsleft after migration153b08f—del provider_config["oauthCredentials"]provider_nameunused variable153b08f— changed to_test-pr.sh1b920d7—+refspec for force-updatea3fe901— 7 tests addedRemaining Findings
1. Edge case in migration:
oauthCredentialspersists whenapi_keyalready setWhen both
api_keyandoauthCredentialsexist (the "already migrated" case),oauthCredentialsis intentionally kept (line 104: migration only fires whenapi_keyis absent). This means staleoauthCredentialsdata lingers in the config dict.If the Pydantic schema ever switches to
extra='forbid', this leftover field would cause a validation error. Currently harmless, but worth noting. Consider always cleaning upoauthCredentialsregardless:Severity: Low — current schema allows extra fields.
2. Test env var isolation uses manual try/finally instead of
monkeypatchTests manipulate
os.environdirectly with try/finally cleanup. Using pytest'smonkeypatchfixture would be more idiomatic and automatically cleaned up:Severity: Minor — current approach works, just less idiomatic.
3.
save_configwrites migrated-away fields backsave_configcallsconfig.model_dump(by_alias=True)which serializes the Pydantic model — this is fine. But if a config was loaded (triggering migration), then saved, theoauthCredentialsfield is gone from the on-disk config permanently. This is the correct behavior (migration is intentionally one-way), but worth being explicit that running nanobot withNANOBOT_CONFIGpointing to a production config will permanently modify it on save. The staging setup avoids this by using a separate config file.Severity: Informational — by design, just worth documenting.
Verified
NANOBOT_CONFIGrespected by bothload_configandsave_configvia sharedget_config_path()_get_oauth_store_dir()intentionally hardcoded to~/.nanobot— staging shares production OAuth credstest-pr.sh+refspec handles re-runs correctlyoauthCredentialsafter extractionapi_keywhen already set (no overwrite)Verdict
Ready to merge. All review feedback addressed. Remaining items are minor/informational and don't block.
🤖 Review by Claude Code
Pull request closed