Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
96 commits
Select commit Hold shift + click to select a range
733cc18
fix(config): define provider identity primitives and duplicate-name v…
PierrunoYT Aug 12, 2026
00b9e87
test(config): cover case-variant upsert rejection
ampagent Aug 12, 2026
7b30aaf
fix provider identity lifecycle boundaries
PierrunoYT Aug 12, 2026
e709787
fix: address provider review feedback
PierrunoYT Aug 12, 2026
82243d3
fix: serialize provider credential updates
PierrunoYT Aug 13, 2026
88cc2fb
fix(config): keep provider transaction out of identity slice
PierrunoYT Aug 14, 2026
4380999
fix(provider): finish the identity contract at every consumer boundary
PierrunoYT Aug 16, 2026
a3b18f5
fix(provider): address CodeRabbit review on the identity contract
PierrunoYT Aug 16, 2026
55f6b48
fix(tui): reconcile live session, saved list, and disk on one rule
PierrunoYT Aug 17, 2026
c3f1f6d
fix(provider): close remaining identity review findings
ampagent Aug 18, 2026
35fe02a
fix(provider): preserve legacy config and OAuth state
PierrunoYT Aug 20, 2026
89b0c6e
docs(provider): document config repair and OAuth validation
PierrunoYT Aug 21, 2026
72d3ea6
fix(provider): make legacy config repairs composable
PierrunoYT Aug 21, 2026
f4a9449
fix(provider): use user-scoped credential store
PierrunoYT Aug 21, 2026
9fc1c03
test(provider): resolve credential lock path portably
PierrunoYT Aug 21, 2026
2b8faf3
fix(config): migrate repaired active provider
PierrunoYT Aug 22, 2026
5ad69c0
fix(provider): resolve identity ownership before mutating persisted rows
PierrunoYT Aug 23, 2026
9d9cf60
feat(providers): resolve catalog ownership and credential candidates
PierrunoYT Aug 12, 2026
c64767b
fix(providers): address credential identity review findings
ampagent Aug 12, 2026
a5c0405
fix(config): fail closed on ambiguous folded provider names
PierrunoYT Aug 14, 2026
028ae99
fix(providers): close remaining credential review gaps
ampagent Aug 18, 2026
3a25e23
fix(provider): fail closed on ambiguous identities
PierrunoYT Aug 21, 2026
84b3fcd
fix(provider): close remaining identity ambiguities
PierrunoYT Aug 21, 2026
0160257
fix(config): adopt legacy catalog-named rows on login
PierrunoYT Aug 23, 2026
542c223
fix(config): bind credential publication to config path
ampagent Aug 27, 2026
9598b0e
fix provider credential review findings
ampagent Aug 27, 2026
b8bf426
fix(providers): transact provider config and keys
ampagent Aug 12, 2026
b67236b
fix(config): pin the credential backend in the cross-process commit test
PierrunoYT Aug 14, 2026
768747b
fix(providers): close transactional review gaps
ampagent Aug 18, 2026
4a39a36
fix(providers): address transactional review findings
PierrunoYT Aug 21, 2026
c837650
fix(providers): resolve remaining transaction review findings
ampagent Aug 21, 2026
c371338
fix(providers): close remaining transaction races
ampagent Aug 22, 2026
e947726
fix(providers): address final review details
ampagent Aug 22, 2026
006e80b
fix(providers): address transaction review findings
PierrunoYT Aug 22, 2026
42f9b81
docs(config): state the allowInvalidInput rule on the write boundary
PierrunoYT Aug 22, 2026
4502b9b
fix(config): preserve catalog adoption in key transaction
ampagent Aug 27, 2026
32a9404
feat(providers): clarify selection source and live sync
ampagent Aug 12, 2026
6a72e14
fix(config): pin credential backends and stop masking catalog owners
PierrunoYT Aug 14, 2026
94b396f
fix(providers): close remaining transaction review gaps
ampagent Aug 18, 2026
f7bb326
fix(providers): address remaining review findings
PierrunoYT Aug 21, 2026
6c844c5
fix(credstore): harden cross-platform file locking
ampagent Aug 21, 2026
c39aaaf
test(credstore): cover transient lock open retries
ampagent Aug 22, 2026
a54bd3b
fix(credstore): stop failing closed on ordinary Windows ACLs
PierrunoYT Aug 22, 2026
0ace75b
fix(credstore): bound the lock-contention test and document the RID l…
PierrunoYT Aug 22, 2026
53c9988
fix(tui): preserve exact live provider selection
ampagent Aug 27, 2026
da822a0
Merge upstream/main into pr4/provider-selection-tui-sync
PierrunoYT Aug 28, 2026
e7ed6f1
fix(providers): scope credential deletion to owning row
PierrunoYT Sep 2, 2026
c29a23d
fix(credstore): accept inherited Windows volume ACLs
ampagent Sep 2, 2026
48c8be0
test(credstore): enforce lock wait deadline
ampagent Sep 2, 2026
ebf8579
fix(providers): respect dictation credential claims
PierrunoYT Sep 3, 2026
806271a
Merge remote-tracking branch 'upstream/main' into pr1/provider-identi…
ampagent Sep 7, 2026
0411bf0
fix(providers): preserve legacy merges and reject cross-layer mutations
ampagent Sep 7, 2026
3ecbda3
Merge refreshed provider identity base into catalog ownership stack
ampagent Sep 7, 2026
c87d6f2
Merge refreshed provider catalog stack and fix logout recovery and le…
ampagent Sep 7, 2026
e6a086c
Merge branch 'pr3/provider-config-key-transaction' into pr4/provider-…
ampagent Sep 7, 2026
337234b
test: isolate provider removal credentials and document key deletion
ampagent Sep 12, 2026
8048208
Merge upstream main into provider identity primitives
ampagent Sep 12, 2026
daf502f
Merge refreshed provider identity base into catalog ownership stack
ampagent Sep 12, 2026
4b5ed9f
Merge validated provider ownership stack into credential transactions
ampagent Sep 12, 2026
dd3c8f4
Merge refreshed provider transaction stack into provider selection sync
ampagent Sep 12, 2026
8427853
fix(config): distinguish blocked provider paths from missing config
ampagent Sep 12, 2026
749a3dd
Merge Windows provider-path classification fix from identity base
ampagent Sep 12, 2026
ccf0630
Merge validated Windows provider-path fix from ownership stack
ampagent Sep 12, 2026
3e81da1
Merge shared Windows persistence fix from provider transaction stack
ampagent Sep 12, 2026
78110c9
fix(providers): preserve identity across repair and session mutations
PierrunoYT Sep 13, 2026
b906276
fix(tui): replace stale removed provider identity
roomote Sep 14, 2026
6b4a190
test(tui): model removed provider fixture
roomote Sep 14, 2026
13c4356
Merge corrected provider identity foundation into PR 4
roomote Sep 14, 2026
8673a02
Merge corrected provider identity foundation into PR 2
roomote Sep 14, 2026
f99da8e
Merge corrected provider ownership stack into PR 3
roomote Sep 14, 2026
6556877
test(providers): keep repair fixture scoped
roomote Sep 14, 2026
fb59458
Merge remote-tracking branch 'origin/pr3/provider-config-key-transact…
PierrunoYT Sep 19, 2026
b5238ee
Merge current main into provider selection stack and isolate test fix…
PierrunoYT Sep 19, 2026
7ebdd48
Merge current main and redact provider transaction errors
PierrunoYT Sep 19, 2026
e218202
Merge current main and isolate provider ownership test fixtures
PierrunoYT Sep 19, 2026
0cd714e
Merge current upstream main into provider identity primitives
ampagent Sep 19, 2026
f187303
fix(tui): release retained provider identity on session resume
ampagent Sep 19, 2026
e13f6e8
Merge refreshed provider identity foundation into catalog ownership
ampagent Sep 19, 2026
85766fa
Merge refreshed catalog ownership into provider transactions
ampagent Sep 19, 2026
a1b644b
Merge refreshed provider transactions into selection synchronization
ampagent Sep 19, 2026
dff3fbf
fix(tui): reconcile stored-key markers after manager deletion
ampagent Sep 20, 2026
010ccd8
Merge provider-manager key reconciliation into catalog ownership
ampagent Sep 20, 2026
2498717
Merge manager key reconciliation at the provider transaction boundary
ampagent Sep 20, 2026
6e23d84
Merge manager key reconciliation into provider selection synchronization
ampagent Sep 20, 2026
6e6c181
fix(tui): preserve live provider identity when resuming sessions
ampagent Sep 25, 2026
83fa9eb
fix: preserve live provider identity across session resume
ampagent Sep 25, 2026
639ac5b
fix(tui): preserve live provider identity across resume
ampagent Sep 25, 2026
808af63
fix(cli): prioritize concrete provider rows over catalog aliases
ampagent Sep 25, 2026
a536157
Merge remote-tracking branch 'review/pr892' into review/pr893-refreshed
ampagent Sep 26, 2026
bab8db7
Merge refreshed provider identity and catalog fixes into transaction …
ampagent Sep 26, 2026
381ae4b
Merge refreshed provider stack into selection and TUI sync
ampagent Sep 26, 2026
b0bab4b
test(tui): isolate manager OAuth credential probes
ampagent Sep 28, 2026
7e754ae
Merge refreshed catalog credential isolation into transaction stack
ampagent Sep 28, 2026
78f08f6
test(config): clarify provider transaction publication results
ampagent Sep 28, 2026
b8b70f6
Merge validated provider transaction stack into selection and live TU…
ampagent Sep 28, 2026
40f5b66
test(credstore): separate contention stress from interactive timeout
ampagent Sep 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
aims to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html) once the first release is
tagged. Until then, source builds report the version `dev`.

## Unreleased

### Features

* **providers:** add `zero providers repair-config [--name <name>]` to recover a single legacy unnamed provider profile while preserving the legacy effective provider and active reference; identity-changing repairs of stored-key profiles refuse before writing, so they cannot point at another credential

### Bug Fixes

* **oauth:** validate provider configuration before authorization and immediately before token replacement across CLI, TUI, setup, and device flows, preserving existing credentials when validation fails
* **providers:** redact credential-shaped values in CLI removal/rename errors and TUI provider-manager ownership and mutation errors

## [0.9.0](https://github.com/Gitlawb/zero/compare/v0.8.0...v0.9.0) (2026-09-15)


Expand Down Expand Up @@ -81,7 +92,6 @@ tagged. Until then, source builds report the version `dev`.
* **modelregistry:** expose reasoning effort for DeepSeek V4 models ([#931](https://github.com/Gitlawb/zero/issues/931)) ([90dcfd1](https://github.com/Gitlawb/zero/commit/90dcfd127e8a6d9902ec9f4e72f6d03fef1a0fc6))
* **providers:** discover ChatGPT capabilities ([#890](https://github.com/Gitlawb/zero/issues/890)) ([2d2450e](https://github.com/Gitlawb/zero/commit/2d2450e9a744349f0d01b1d4e9ba29c24ba5650d))
* **sandbox:** normalize launcher names before the command-prefix denylist ([#934](https://github.com/Gitlawb/zero/issues/934)) ([6edf9a8](https://github.com/Gitlawb/zero/commit/6edf9a8b78dc030dc44598919db1c7fa9d4f809a))

## [0.7.0](https://github.com/Gitlawb/zero/compare/v0.6.0...v0.7.0) (2026-08-10)


Expand Down
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,14 @@ zero models list
zero doctor
```

If an upgraded `config.json` contains one legacy provider profile without a
name, repair it with `zero providers repair-config`. The command preserves the
saved `activeProvider` name (falling back to `openai`). If a row already has that
exact name, the command preserves the field merge older releases used, with
later nonempty fields taking precedence. An explicit `--name <unique-name>`
keeps the legacy row separate instead. Case-only collisions and multiple
unnamed rows are not merged by guessing; follow the error's repair guidance.

For API providers, set the matching environment variable before setup or enter
the key in the wizard:

Expand Down Expand Up @@ -307,7 +315,7 @@ zero exec one-shot or scripted agent run
zero setup first-run provider setup
zero auth OAuth/login helpers for supported providers
zero models model registry and capabilities
zero providers provider profiles and detection
zero providers provider profiles, recovery, and detection
zero doctor setup, key, and connectivity checks
zero context context-budget report
zero repo-map deterministic repository map
Expand Down
10 changes: 9 additions & 1 deletion README_ZH.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,14 @@ zero models list
zero doctor
```

如果升级后的 `config.json` 中有一个旧版未命名的提供商配置,请运行
`zero providers repair-config` 进行修复。该命令会保留已保存的
`activeProvider` 名称(未设置时回退到 `openai`)。如果已有配置行使用完全相同的
名称,该命令会保留旧版本的字段合并规则,后出现的非空字段优先。
通过 `--name <唯一名称>` 显式指定名称则会将旧配置行单独保留。
对于仅大小写不同的名称冲突或多个未命名的配置行,Zero 不会猜测并合并;
请按照错误消息中的修复指引操作。

对于 API 提供商,在设置之前设置匹配的环境变量或在向导中输入密钥:

```bash
Expand Down Expand Up @@ -212,7 +220,7 @@ zero exec 一次性或脚本化智能体运行
zero setup 首次运行提供商设置
zero auth 支持提供商的 OAuth/登录辅助
zero models 模型注册表和能力
zero providers 提供商配置和检测
zero providers 提供商配置、修复和检测
zero doctor 设置、密钥和连接检查
zero context 上下文预算报告
zero repo-map 确定性仓库映射
Expand Down
7 changes: 7 additions & 0 deletions docs/oauth-subscriptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ When a login exists for a provider, the **OpenAI and Anthropic** providers send
before. Tokens are stored 0600 (or the OS keyring with
`ZERO_OAUTH_STORAGE=keyring`) and never logged. See `zero auth --help`.

Provider OAuth login validates the persisted user configuration before opening
authorization and revalidates it immediately before replacing a stored token.
This applies to CLI login, the TUI/setup wizard, and device-code completion. If
the configuration is invalid at either check, Zero aborts without overwriting
the previous OAuth credential. If the error identifies one legacy unnamed
provider profile, repair it with `zero providers repair-config`.

### In the setup wizard (`/provider`)

Running `/provider` opens a **"How do you want to connect?"** chooser:
Expand Down
10 changes: 8 additions & 2 deletions internal/cli/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/Gitlawb/zero/internal/localcontrol"
"github.com/Gitlawb/zero/internal/mcp"
"github.com/Gitlawb/zero/internal/modelregistry"
"github.com/Gitlawb/zero/internal/oauth"
"github.com/Gitlawb/zero/internal/observability"
"github.com/Gitlawb/zero/internal/peermsg"
"github.com/Gitlawb/zero/internal/plugins"
Expand Down Expand Up @@ -69,6 +70,7 @@ type appDeps struct {
discoverProviderModels func(context.Context, config.ProviderProfile) ([]providermodeldiscovery.Model, error)
detectLocalRuntimes func(context.Context, provideronboarding.LocalDetectOptions) []provideronboarding.DetectedLocalRuntime
openRouterLogin func(context.Context, provideroauth.OpenRouterOptions) (string, error)
chatGPTLogin func(context.Context, provideroauth.ChatGPTOptions) (oauth.Token, error)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
newSessionStore func() *sessions.Store
loadPlugins func(plugins.LoadOptions) (plugins.LoadResult, error)
loadHooks func(hooks.LoadOptions) (hooks.LoadResult, error)
Expand Down Expand Up @@ -172,6 +174,7 @@ func defaultAppDeps() appDeps {
discoverProviderModels: defaultDiscoverProviderModels,
detectLocalRuntimes: provideronboarding.DetectLocalRuntimes,
openRouterLogin: provideroauth.OpenRouterLogin,
chatGPTLogin: provideroauth.ChatGPTLogin,
newSessionStore: func() *sessions.Store {
return sessions.NewStore(sessions.StoreOptions{})
},
Expand Down Expand Up @@ -545,6 +548,9 @@ func fillAppDeps(deps appDeps) appDeps {
if deps.openRouterLogin == nil {
deps.openRouterLogin = defaults.openRouterLogin
}
if deps.chatGPTLogin == nil {
deps.chatGPTLogin = defaults.chatGPTLogin
}
if deps.newSessionStore == nil {
deps.newSessionStore = defaults.newSessionStore
}
Expand Down Expand Up @@ -739,8 +745,8 @@ func runInteractiveTUIWithSetup(stderr io.Writer, deps appDeps, permissionMode a
// the encrypted credential store (interactive runs only; headless exec keeps
// its existing behavior). Non-fatal — a missing keyring or write error leaves
// the inline key in place and this run still uses the already-resolved key.
if store, storeErr := config.ProviderKeyStoreAt(filepath.Dir(userConfigPath)); storeErr == nil {
_, _ = config.MigratePlaintextProviderKeys(userConfigPath, store)
if _, migrationErr := config.MigratePlaintextProviderKeysTransactional(userConfigPath); migrationErr != nil {
_, _ = fmt.Fprintf(stderr, "[zero] warning: could not migrate every plaintext provider API key: %s\n", redaction.ErrorMessage(migrationErr, redaction.Options{}))
}
doctorUserConfigPath := ""
projectConfigPath := ""
Expand Down
47 changes: 47 additions & 0 deletions internal/cli/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"io"
"os"
"path/filepath"
"reflect"
"runtime"
"strings"
"testing"
Expand All @@ -17,6 +18,8 @@ import (
"github.com/Gitlawb/zero/internal/agent"
"github.com/Gitlawb/zero/internal/config"
"github.com/Gitlawb/zero/internal/mcp"
"github.com/Gitlawb/zero/internal/oauth"
"github.com/Gitlawb/zero/internal/provideroauth"
"github.com/Gitlawb/zero/internal/redaction"
"github.com/Gitlawb/zero/internal/tools"
"github.com/Gitlawb/zero/internal/tui"
Expand All @@ -33,6 +36,29 @@ func (failingWriter) Write([]byte) (int, error) {
return 0, errWriteFailed
}

func TestChatGPTLoginDependencyDefaultsAndPreservesInjection(t *testing.T) {
wantDefault := reflect.ValueOf(provideroauth.ChatGPTLogin).Pointer()
if got := defaultAppDeps().chatGPTLogin; got == nil || reflect.ValueOf(got).Pointer() != wantDefault {
t.Fatal("defaultAppDeps().chatGPTLogin is not provideroauth.ChatGPTLogin")
}
if got := fillAppDeps(appDeps{}).chatGPTLogin; got == nil || reflect.ValueOf(got).Pointer() != wantDefault {
t.Fatal("fillAppDeps(appDeps{}).chatGPTLogin is not provideroauth.ChatGPTLogin")
}

called := false
injected := func(context.Context, provideroauth.ChatGPTOptions) (oauth.Token, error) {
called = true
return oauth.Token{}, nil
}
deps := fillAppDeps(appDeps{chatGPTLogin: injected})
if _, err := deps.chatGPTLogin(context.Background(), provideroauth.ChatGPTOptions{}); err != nil {
t.Fatalf("injected chatGPTLogin returned error: %v", err)
}
if !called {
t.Fatal("fillAppDeps replaced the injected chatGPTLogin")
}
}

func TestRunPrintsVersion(t *testing.T) {
var stdout bytes.Buffer
var stderr bytes.Buffer
Expand Down Expand Up @@ -404,6 +430,27 @@ func TestRunNoArgsFailsWhenResolveErrorIsNotProviderRelated(t *testing.T) {
}
}

func TestRunNoArgsOffersRepairCommandForPersistedNameFailure(t *testing.T) {
var stdout, stderr bytes.Buffer
cwd := t.TempDir()
configPath := filepath.Join(t.TempDir(), "zero", "config.json")
writeProviderOnboardingConfig(t, configPath, config.FileConfig{Providers: []config.ProviderProfile{{Name: ""}, {Name: "work"}, {Name: "WORK"}}})
exitCode := runWithDeps(nil, &stdout, &stderr, appDeps{
getwd: func() (string, error) { return cwd, nil },
userConfigPath: func() (string, error) { return configPath, nil },
resolveConfig: func(string, config.Overrides) (config.ResolvedConfig, error) {
return config.Resolve(config.ResolveOptions{UserConfigPath: configPath, Env: map[string]string{}})
},
runTUI: func(context.Context, tui.Options) int {
t.Fatal("TUI must not launch with ambiguous persisted identities")
return 0
},
})
if exitCode == exitSuccess || !strings.Contains(stderr.String(), "zero providers repair-config") {
t.Fatalf("exit=%d stderr=%q, want actionable repair path", exitCode, stderr.String())
}
}

func TestRunNoArgsLaunchesTUIWithMCPState(t *testing.T) {
var stdout bytes.Buffer
var stderr bytes.Buffer
Expand Down
Loading
Loading