fix(app-bind): self-heal orphaned runtime-proxy bind with no backup (#614) - #615
Conversation
…614) When app-bind rewrote ~/.codex/config.toml to the runtime-proxy provider but its state/backup files were later lost, the config stayed bound while `getAppBindStatus` and `unbind-app` — which inferred "bound" purely from the state files — reported "not configured" and refused to act. The user was left with a config pointing at a dead proxy port, recoverable only by hand-editing config.toml. Fixes: - config-toml: add `configHasRuntimeRotationProvider()` (detect a bound config from the top-level model_provider or the proxy provider block) and `restoreConfigTomlFromRuntimeRotationProviderWithoutBackup()` (strip the proxy block and fall back the top-level provider to "openai" when no original backup exists; pins line endings to the input style). - app-bind: `unbindCodexAppRuntimeRotationLocked` now self-heals — when there is no backup and no state but config.toml is still bound, it restores the config and reports the recovery. - app-bind: `getAppBindStatus` derives `bound` from the config when no state file is present and exposes a new `unmanagedBind` flag; `formatAppBindStatus` surfaces "bound but unmanaged" with the unbind remedy instead of "not configured". Adds 11 regression tests (detection, no-backup restore incl. CRLF, unmanaged-status detection, self-heal unbind, clean-config no-op). Full suite: 4947 passed, 3 skipped. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
|
Warning Review limit reached
More reviews will be available in 7 minutes and 14 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughadds orphaned app-bind recovery for issue ChangesOrphaned bind detection and self-healing unbind
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested labels
review flagswindows/crlf robustness at concurrency gap at
missing regression test: no test covers the case where 🚥 Pre-merge checks | ✅ 2 | ❌ 3❌ Failed checks (3 warnings)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify code
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@coderabbitai review |
✅ Action performedReview finished.
|
…ery (#614) Greptile review (P1) on #615: when the proxy provider block was present but the top-level model_provider already pointed at a non-proxy value (e.g. a half-orphaned config, or one where unbind partially ran), the no-backup recovery path duplicated the model_provider key — invalid TOML that codex-cli refuses to parse, leaving the user worse off. Root cause was the shared restoreTopLevelModelProvider: its fallback splice fired whenever no *proxy* model_provider line was found, even when a valid non-proxy top-level line already existed. Guard the splice so it only inserts the original line when there is no top-level model_provider at all; an existing line is left untouched (removing the proxy block is sufficient). Adds the two P2 tests Greptile requested: half-orphan recovery (asserts a single model_provider line) and disable_response_storage cleanup in the no-backup path. Full suite: 4949 passed, 3 skipped. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@lib/runtime/config-toml.ts`:
- Around line 313-334: The root bug is in the
`restoreConfigTomlFromRuntimeRotationProviderWithoutBackup` function at
lib/runtime/config-toml.ts#L313-L334: it unconditionally synthesizes a
`model_provider` line without checking if the current config already has a
non-proxy provider, causing duplicate keys in the output. Fix this by using
`extractTopLevelModelProviderLine` to check if `currentConfig` already contains
a non-proxy top-level `model_provider` before synthesizing the
`syntheticOriginal`; if a non-proxy provider already exists, set
`syntheticOriginal` to an empty string (or just the line ending) to preserve the
existing line instead of duplicating it. At lib/runtime/app-bind.ts#L809-L824,
the same function is called for self-healing; no direct code change is needed
there since the fix propagates from the root cause fix. Add a regression test at
test/config-toml-restore.test.ts#L150-L192 that restores a partial-orphan config
(one with both a non-proxy `model_provider` and a proxy block) and asserts the
output has exactly one `model_provider` line with no proxy block. Add another
regression test at test/app-bind.test.ts#L858-L875 that seeds a partial-orphan
config, calls `unbindCodexAppRuntimeRotation`, and asserts the restored config
has exactly one `model_provider` line (the existing non-proxy one) with no
duplicates and no proxy block.
In `@test/app-bind.test.ts`:
- Around line 858-875: Add a new test case in the test file (in addition to the
existing "self-heals a bound config with no backup/state on unbind" test) that
covers the partial-orphan recovery scenario. Create a test helper function
(similar to seedOrphanedBind) that seeds a config containing both a non-proxy
model_provider line set to "openai" AND a
[model_providers.codex-multi-auth-runtime-proxy] block, then call
unbindCodexAppRuntimeRotation with the same parameters as the existing test.
Assert that the restored config contains exactly one model_provider line with
the value "openai", does not contain any duplicate model_provider entries, and
does not contain the proxy block, to verify that
restoreConfigTomlFromRuntimeRotationProviderWithoutBackup handles this scenario
correctly without creating duplicates.
- Around line 858-875: The test for orphaned-bind recovery is missing a
verification that the injected disable_response_storage = false line is properly
removed during restoration. Add an expect statement to the test between line 858
and 875 that verifies the restored config does not contain the
disable_response_storage = false line, similar to how the test already checks
that codex-multi-auth-runtime-proxy is removed. This ensures that the
restoreTopLevelResponseStorage function properly cleans up the synthetic config
injection when recovering from an orphaned bind state.
In `@test/config-toml-restore.test.ts`:
- Around line 150-192: Add a new test case within the
restoreConfigTomlFromRuntimeRotationProviderWithoutBackup describe block to
verify recovery of a partial-orphan config (one that has a non-proxy
model_provider like "openai" AND a proxy block like
[model_providers.codex-multi-auth-runtime-proxy]). The test should pass a config
with model_provider = "openai" and the proxy block through the
restoreConfigTomlFromRuntimeRotationProviderWithoutBackup function, then assert
that the restored output contains exactly one model_provider line (the existing
"openai" one), does not contain the proxy block, and that
configHasRuntimeRotationProvider returns false for the result.
- Around line 150-192: Add a new test case within the
"restoreConfigTomlFromRuntimeRotationProviderWithoutBackup" describe block that
verifies cleanup of the disable_response_storage setting during orphaned
recovery. Create a test that defines a bound config string containing both the
runtime proxy model_provider and disable_response_storage set to false, calls
restoreConfigTomlFromRuntimeRotationProviderWithoutBackup with this bound
config, and then asserts that the restored result does not contain the
disable_response_storage line. This test should ensure that when the synthetic
original config doesn't include disable_response_storage, the
restoreTopLevelResponseStorage function properly removes this injected setting
during the no-backup restore path.
- Around line 150-192: The test suite is missing coverage for cleanup of the
`disable_response_storage = false` line that gets injected by
`enableTopLevelResponseStorage` during bind. This line should be removed during
restoration via `restoreConfigTomlFromRuntimeRotationProviderWithoutBackup`. In
test/config-toml-restore.test.ts at lines 150-192, add a test case to the
restoreConfigTomlFromRuntimeRotationProviderWithoutBackup describe block that
verifies a bound config containing `disable_response_storage = false` is
properly cleaned up after calling the restore function. In test/app-bind.test.ts
at lines 858-875, add a corresponding test case that verifies the same cleanup
occurs during orphaned recovery when calling `unbindCodexAppRuntimeRotation`,
ensuring that the injected line doesn't leave residue in either recovery
scenario.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: ebb996a0-942f-474a-971d-b3c0f929761a
📒 Files selected for processing (4)
lib/runtime/app-bind.tslib/runtime/config-toml.tstest/app-bind.test.tstest/config-toml-restore.test.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (14)
test/**/*.test.ts
📄 CodeRabbit inference engine (test/AGENTS.md)
test/**/*.test.ts: Write Vitest test suites with globals enabled (describe, it, expect)
Maintain 80%+ coverage threshold across statements, branches, functions, and lines
Use removeWithRetry() for Windows filesystem cleanup instead of bare fs.rm to handle EBUSY, EPERM, and ENOTEMPTY errors
Do not rely on dist/ in tests; use source files instead
Do not skip tests without justification
Relax lint rules for test files as configured in eslint.config.js
Files:
test/app-bind.test.tstest/config-toml-restore.test.ts
!{dist/**,**/.*,**/node_modules/**}
📄 CodeRabbit inference engine (AGENTS.md)
Store source code in root
index.ts,lib/, andscripts/directories; never editdist/or local temp/cache directories as they are generated output
Files:
test/app-bind.test.tstest/config-toml-restore.test.tslib/runtime/config-toml.tslib/runtime/app-bind.ts
**/*.{ts,tsx,js,mjs,cjs}
📄 CodeRabbit inference engine (AGENTS.md)
Use ESM only with
"type": "module"configuration and require Node >= 18.17
Files:
test/app-bind.test.tstest/config-toml-restore.test.tslib/runtime/config-toml.tslib/runtime/app-bind.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Do not use TypeScript type assertions (
as any,@ts-ignore, or@ts-expect-error)
Files:
test/app-bind.test.tstest/config-toml-restore.test.tslib/runtime/config-toml.tslib/runtime/app-bind.ts
**/*.{js,ts,tsx,jsx,json}
📄 CodeRabbit inference engine (README.md)
**/*.{js,ts,tsx,jsx,json}: UseCODEX_MULTI_AUTH_DIRenvironment variable to override the default settings and accounts storage root from~/.codex/multi-auth/
UseCODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY=0/1to disable or enable the default-on live Responses proxy rotation for forwarded Codex CLI/app sessions
KeepCODEX_MODE=0/1environment variable for disabling/enabling Codex mode in runtime
Files:
test/app-bind.test.tstest/config-toml-restore.test.tslib/runtime/config-toml.tslib/runtime/app-bind.ts
**/*.{js,ts,tsx,jsx}
📄 CodeRabbit inference engine (README.md)
**/*.{js,ts,tsx,jsx}: Implement stateful Responsesbackground: truecompatibility only when opt-in viabackgroundResponsessetting orCODEX_AUTH_BACKGROUND_RESPONSES=1environment variable
Set request timeout behavior usingCODEX_AUTH_FETCH_TIMEOUT_MSand stream stall timeout usingCODEX_AUTH_STREAM_STALL_TIMEOUT_MSenvironment variables
UseCODEX_TUI_V2=0/1,CODEX_TUI_COLOR_PROFILE, andCODEX_TUI_GLYPHSenvironment variables to control terminal UI appearance and capabilities
Store accounts data inopenai-codex-accounts.jsonfile within the configured storage root, with per-project account files under~/.codex/multi-auth/projects/<project-key>/
Store flagged accounts separately inopenai-codex-flagged-accounts.jsonto support account recovery and repair workflows
Implement quota caching by writing to and reading from~/.codex/multi-auth/quota-cache.json
Track runtime observability metrics in~/.codex/multi-auth/runtime-observability.jsonwith request counters, budget state, and multi-auth probe visibility
Record local usage in~/.codex/multi-auth/usage/usage-ledger.jsonlusing JSONL (newline-delimited JSON) format for each usage event
Store account policies in~/.codex/multi-auth/account-policies.jsonto enable policy controls and account capability views
Store routing profiles in~/.codex/multi-auth/routing-profiles.jsonfor multi-auth routing configuration
Store budget guards in~/.codex/multi-auth/budget-guards.jsonto implement runtime budget constraints and prevent over-spending
Store local client bridge tokens in~/.codex/multi-auth/local-client-tokens.jsonwith hashed values for/health,/v1/models, and/v1/responsesendpoint protection
Implement all dashboard navigation using the specified hotkeys: Up/Down for movement, Enter for selection, 1-9 for quick switch, / for search, ? for help, Q for back/cancel
Implement account details dashboard with the specified hotkeys: S for set account, R for refresh/re-login, E fo...
Files:
test/app-bind.test.tstest/config-toml-restore.test.tslib/runtime/config-toml.tslib/runtime/app-bind.ts
test/**
⚙️ CodeRabbit configuration file
tests must stay deterministic and use vitest. demand regression cases that reproduce concurrency bugs, token refresh races, and windows filesystem behavior. reject changes that mock real secrets or skip assertions.
Files:
test/app-bind.test.tstest/config-toml-restore.test.ts
**
⚙️ CodeRabbit configuration file
**: # PROJECT KNOWLEDGE BASEGenerated: 2026-04-25
Commit: a87e005
Validated: 2026-06-10 against commit 98d9819 (repo audit; claims re-checked against the tree, content not regenerated)
Branch: main
Package version: 2.3.1OVERVIEW
codex-multi-authis a Codex CLI-first OAuth account manager and optional forwarding wrapper for the official Codex CLI. The installedcodex-multi-authentrypoint handles account-management commands locally,codex-multi-auth-codexforwards official Codex commands through this package's wrapper when explicitly used, and runtime rotation can route live Responses traffic through a localhost account-rotation proxy by default. The plugin-host entrypoint remains exported for compatibility, but the primary product surface is the account manager, optional wrapper, storage, runtime proxy, and repair tooling.STRUCTURE
./ ├── scripts/ │ ├── codex.js # codex-multi-auth-codex wrapper, official CLI forwarder, shadow CODEX_HOME/runtime proxy setup │ ├── codex-multi-auth.js # standalone package CLI entrypoint │ ├── codex-routing.js # auth command and compatibility alias routing │ ├── codex-bin-resolver.js # official Codex binary discovery │ ├── codex-app-router.js # persistent localhost router for packaged Codex app bind │ └── codex-app-launcher.js # reversible user-level app launcher routing helper ├── index.ts # optional plugin-host runtime entry ├── lib/ # core runtime logic (see lib/AGENTS.md) │ ├── auth/ # OAuth flow, PKCE, callback server │ ├── runtime/ # Codex CLI/app integration helpers, app bind, live sync, runtime observability │ ├── request/ # request transform, SSE, failover, backoff │ ├── storage/ # path resolution, migrations, backups, restore, import/export │ ├── codex-cli/ # Codex CLI state sync and writer helpers │ ├── codex-manager/ # command modules and settin...
Files:
test/app-bind.test.tstest/config-toml-restore.test.tslib/runtime/config-toml.tslib/runtime/app-bind.ts
lib/**/*.ts
📄 CodeRabbit inference engine (lib/AGENTS.md)
lib/**/*.ts: Runtime rotation code must preserve pass-through semantics except for auth/provider headers that intentionally change
Never import fromdist/in source tests or library code
Files:
lib/runtime/config-toml.tslib/runtime/app-bind.ts
lib/**/runtime/**/*.ts
📄 CodeRabbit inference engine (lib/AGENTS.md)
lib/**/runtime/**/*.ts: Runtime proxy client-facing headers must not expose account emails or tokens
Runtime rotation should fail open to normal official Codex forwarding when startup helpers are unavailable
Never add account emails/tokens to runtime proxy client responses
Files:
lib/runtime/config-toml.tslib/runtime/app-bind.ts
lib/**/*.{ts,tsx}
📄 CodeRabbit inference engine (lib/AGENTS.md)
Never suppress type errors
Files:
lib/runtime/config-toml.tslib/runtime/app-bind.ts
**/runtime/**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
Do not expose account emails or tokens in runtime proxy client response headers or logs
Files:
lib/runtime/config-toml.tslib/runtime/app-bind.ts
{lib/runtime/**/*.ts,lib/codex-manager/commands/status.ts}
📄 CodeRabbit inference engine (AGENTS.md)
Runtime rotation provider ID is
codex-multi-auth-runtime-proxyand status command iscodex-multi-auth rotation status
Files:
lib/runtime/config-toml.tslib/runtime/app-bind.ts
lib/**
⚙️ CodeRabbit configuration file
focus on auth rotation, windows filesystem IO, and concurrency. verify every change cites affected tests (vitest) and that new queues handle EBUSY/429 scenarios. check for logging that leaks tokens or emails.
Files:
lib/runtime/config-toml.tslib/runtime/app-bind.ts
🧠 Learnings (2)
📚 Learning: 2026-06-04T06:14:18.093Z
Learnt from: ndycode
Repo: ndycode/codex-multi-auth PR: 510
File: test/scheduling-strategy-config.test.ts:1-1
Timestamp: 2026-06-04T06:14:18.093Z
Learning: In ndycode/codex-multi-auth, do not flag explicit imports from "vitest" (e.g., describe, it, expect, beforeEach/afterEach, etc.) in test files as issues—even if the Vitest config sets `globals: true`. The repo’s established convention is to keep these imports for consistency with neighboring tests; removing them would make files outliers.
Applied to files:
test/app-bind.test.tstest/config-toml-restore.test.ts
📚 Learning: 2026-06-04T06:14:24.975Z
Learnt from: ndycode
Repo: ndycode/codex-multi-auth PR: 510
File: test/runtime-rotation-proxy.test.ts:2478-2491
Timestamp: 2026-06-04T06:14:24.975Z
Learning: In ndycode/codex-multi-auth test files (e.g. `test/*.test.ts`), when creating V3 storage fixtures for accounts, it’s an intentional convention to use `as never` for deliberately minimal stored-account objects that only include `refreshToken`, `addedAt`, and `lastUsed`. Do not treat `as never` here as a type-safety problem: optional/other fields are expected to be populated by the runtime during execution, and the cast is used solely to keep the fixture minimal and consistent across existing tests.
Applied to files:
test/app-bind.test.tstest/config-toml-restore.test.ts
🔇 Additional comments (9)
lib/runtime/config-toml.ts (1)
275-303: LGTM!test/config-toml-restore.test.ts (1)
119-148: LGTM!lib/runtime/app-bind.ts (4)
12-14: LGTM!
86-92: LGTM!
624-640: LGTM!
855-864: LGTM!test/app-bind.test.ts (3)
12-12: LGTM!
818-856: LGTM!
877-902: LGTM!
Greptile/CodeRabbit follow-up: add an integration-level regression that drives the partial-orphan case (proxy block present, top-level model_provider already native) through unbindCodexAppRuntimeRotation and asserts exactly one model_provider line survives — complementing the unit-level coverage of restoreConfigTomlFromRuntimeRotationProviderWithoutBackup. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@coderabbitai the duplicate-
Full suite green (4949+). Please re-review at |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Check current state of restoreTopLevelModelProvider in config-toml.ts
rg -n "restoreTopLevelModelProvider\|configHasRuntimeRotationProvider\|restoreConfigTomlFromRuntimeRotationProviderWithoutBackup\|model_provider" lib/runtime/config-toml.ts
echo "---"
# Check new tests for half-orphan and disable_response_storage in config-toml-restore test
rg -n "half.orphan\|disable_response_storage\|exactly one\|model_provider" test/config-toml-restore.test.ts
echo "---"
# Check integration-level half-orphan in app-bind test
rg -n "half.orphan\|exactly one\|model_provider\|unbindCodexApp" test/app-bind.test.tsLength of output: 872 🏁 Script executed: #!/bin/bash
# Find relevant source and test files
fd -e ts "config-toml|app-bind" --full-path
echo "==="
fd -e ts "config.toml|app.bind" --full-path
echo "==="
# Also check git log to confirm commits
git log --oneline -10
echo "==="
# Find any ts files in lib/runtime
fd -e ts . lib/ 2>/dev/null | head -30
echo "==="
fd -e ts . test/ 2>/dev/null | head -30Length of output: 3208 🏁 Script executed: #!/bin/bash
echo "=== config-toml.ts: restoreTopLevelModelProvider and related ==="
grep -n "restoreTopLevelModelProvider\|model_provider\|configHasRuntimeRotation\|restoreConfigToml\|disable_response_storage\|half.orphan\|RUNTIME_ROTATION_PROXY" lib/runtime/config-toml.ts | head -60
echo "=== full restoreTopLevelModelProvider function ==="
grep -n "" lib/runtime/config-toml.ts | sed -n '/restoreTopLevelModelProvider/,/^[0-9]*:}/p' | head -40
echo "=== config-toml-restore.test.ts: new tests ==="
grep -n "half.orphan\|disable_response_storage\|exactly one\|model_provider\|without.backup\|CRLF\|it(\|describe(" test/config-toml-restore.test.ts | head -50
echo "=== app-bind.test.ts: new integration tests ==="
grep -n "half.orphan\|exactly one\|model_provider\|it(\|describe(" test/app-bind.test.ts | head -50Length of output: 12362 🏁 Script executed: #!/bin/bash
echo "=== app-bind.test.ts half-orphan test full body ==="
sed -n '877,930p' test/app-bind.test.ts
echo "=== config-toml-restore.test.ts no-backup tests ==="
sed -n '150,230p' test/config-toml-restore.test.tsLength of output: 5333
no outstanding gaps. happy to approve. [approve] |
…ind (#614) CodeRabbit follow-up: the orphaned-bind fixture now also carries the bind-injected `disable_response_storage = false` line, and the self-heal unbind test asserts it is removed during recovery (integration-level coverage to match the unit-level test in config-toml-restore). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Patch release with self-healing recovery for an orphaned runtime-proxy app-bind (#614, #615). No runtime-rotation, storage, or auth behavior changed. - Bump version to 2.3.2 across package.json, package-lock.json, .codex-plugin/plugin.json, and AGENTS.md - Add docs/releases/v2.3.2.md - Promote v2.3.2 to current stable in README and docs portal; demote v2.3.1 to prior stable - Add CHANGELOG entry Full suite green (4950 passed, 3 skipped); typecheck clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fixes #614.
Problem
When app-bind rewrites the real
~/.codex/config.tomlto the runtime-proxy provider but its state/backup files are later lost (cleanup, partial unbind, a marker-less re-run of first-run setup, crash), the config stays bound while the tooling goes blind:getAppBindStatusandrotation statusinfer "bound" only fromapp-bind/*.json, so they report "not configured" even thoughconfig.tomlstill hasmodel_provider = "codex-multi-auth-runtime-proxy"+ the proxy block.unbind-appkeys restoration off the backup file, so with no backup it's a no-op ("Codex app bind was not configured").Net: the user's Codex CLI/Desktop is routed to a dead proxy port with no automated recovery — hand-editing
config.tomlwas the only fix. (Reproduced live during 2.3.1 smoke testing; it bound the real config twice.)Fix
lib/runtime/config-toml.tsconfigHasRuntimeRotationProvider(rawConfig)— detects a bound config from either the top-levelmodel_provideror the proxy[model_providers.<id>]block (top-level scan stops at the first table, so a stray mention elsewhere isn't a false positive).restoreConfigTomlFromRuntimeRotationProviderWithoutBackup(currentConfig, defaultProvider = "openai")— strips the proxy block and, with no original backup line to restore, falls the top-level provider back toopenai. Pins line endings to the input style (fixes a CRLF-collapse edge).lib/runtime/app-bind.tsunbindCodexAppRuntimeRotationLockednow self-heals: when there's no backup and no state butconfig.tomlis still bound, it restores the config directly and reportsRestored Codex app config … from an orphaned runtime-proxy bind (no backup was present).getAppBindStatusderivesboundfrom the config when no state file exists and exposes a newunmanagedBindflag.formatAppBindStatussurfaces "bound but unmanaged" with theunbind-appremedy instead of "not configured".Testing
config-toml-restore.test.ts(detection true/false/empty/stray-mention; no-backup restore incl. custom provider + CRLF) andapp-bind.test.ts(unmanaged-status detection, self-heal unbind end-to-end, clean-config no-op).unbind-app→ restoresmodel_provider = "openai", removes the proxy block, preserves other sections; after →bound=false, unmanaged=false.npm run build,typecheck, eslint clean. Full suite: 4947 passed, 3 skipped (was 4936).Release
Targets
main; will ship in the next patch (2.3.2). Can't go into the already-published 2.3.1.🤖 Generated with Claude Code
note: greptile review for oc-chatgpt-multi-auth. cite files like
lib/foo.ts:123. confirm regression tests + windows concurrency/token redaction coverage.Greptile Summary
fixes the orphaned app-bind scenario where
config.tomlstayed bound to the runtime proxy after state/backup files were lost, leaving the user stuck on a dead port with no automated recovery path.configHasRuntimeRotationProviderletsgetAppBindStatusandunbind-appconsult the config directly when no state file is present, exposing a newunmanagedBindflag and "bound but unmanaged" status message.restoreConfigTomlFromRuntimeRotationProviderWithoutBackupsynthesizes a minimal original config (model_provider = "openai") and delegates to the shared restore path; correctly handles the half-orphan case (proxy block present, top-level provider already native) to avoid producing duplicate TOML keys, and pins CRLF line endings for Windows-authored configs.disable_response_storagecleanup, and the full self-heal unbind flow.Confidence Score: 5/5
safe to merge; recovery logic is correct, the half-orphan duplicate-key fix is test-covered, and CRLF handling is explicit for Windows configs
the core detection and restore functions are well-isolated and tested across all meaningful variants. the only gap is that startup-registration files (launchAgent/Windows startup) are not attempted during orphaned recovery, which could leave a stale startup entry after a crash-orphan — but this doesn't corrupt data or block normal operation
lib/runtime/app-bind.ts — the orphaned self-heal branch at line 809 skips startup file cleanup; worth a follow-up to attempt best-effort deletion of paths.startupPath and paths.launchAgentPath in the existing cleanup loop
Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A[unbindCodexAppRuntimeRotation] --> B[withAppBindLock] B --> C[readAppBindState] C --> D{state exists?} D -- yes --> E[stopRouter / removeAppBindStartup] E --> F[readAppBindBackup] D -- no --> F F --> G{backup exists?} G -- yes --> H[restoreConfigTomlFromAppBind\nusing backup content] G -- no --> I{state exists?} I -- yes --> J[restoreConfigTomlFromAppBind\nusing empty backup] I -- no --> K[readConfigIfExists paths.configPath] K --> L{configHasRuntimeRotationProvider?} L -- yes --> M[restoreConfigTomlFromRuntimeRotationProviderWithoutBackup\nself-heal orphaned bind] L -- no --> N[no-op: not configured] M --> O[selfHealed = true] H --> P[cleanup statePath / backupPath / statusPath] J --> P O --> P N --> P P --> Q[getAppBindStatus] Q --> R{state === null?} R -- yes --> S[readConfigIfExists\nconfigHasRuntimeRotationProvider] S --> T[unmanagedBind = true/false] R -- no --> U[unmanagedBind = false] T --> V[return AppBindStatus] U --> V%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%% flowchart TD A[unbindCodexAppRuntimeRotation] --> B[withAppBindLock] B --> C[readAppBindState] C --> D{state exists?} D -- yes --> E[stopRouter / removeAppBindStartup] E --> F[readAppBindBackup] D -- no --> F F --> G{backup exists?} G -- yes --> H[restoreConfigTomlFromAppBind\nusing backup content] G -- no --> I{state exists?} I -- yes --> J[restoreConfigTomlFromAppBind\nusing empty backup] I -- no --> K[readConfigIfExists paths.configPath] K --> L{configHasRuntimeRotationProvider?} L -- yes --> M[restoreConfigTomlFromRuntimeRotationProviderWithoutBackup\nself-heal orphaned bind] L -- no --> N[no-op: not configured] M --> O[selfHealed = true] H --> P[cleanup statePath / backupPath / statusPath] J --> P O --> P N --> P P --> Q[getAppBindStatus] Q --> R{state === null?} R -- yes --> S[readConfigIfExists\nconfigHasRuntimeRotationProvider] S --> T[unmanagedBind = true/false] R -- no --> U[unmanagedBind = false] T --> V[return AppBindStatus] U --> VReviews (3): Last reviewed commit: "test(app-bind): assert disable_response_..." | Re-trigger Greptile