Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,19 @@ public enum QwenCloudProviderDescriptor {

static func makeDescriptor() -> ProviderDescriptor {
#if os(macOS)
let browserOrder: BrowserCookieImportOrder = [.chrome]
// AGENTS.md L48: "Cookie imports: default Chrome-only when possible to
// avoid other browser prompts; override via browser list when needed."
// The override here is the minimum necessary: Chrome + Brave. The full
// 7-browser list (chromeBeta, edge, arc, firefox, safari) is
// deliberately omitted so automatic refreshes do not surface unwanted
// Keychain / browser-store access prompts on browsers that don't carry
// a Qwen Cloud session. Brave is kept because it shares the same
// Chromium Safe Storage format as Chrome and is a common Qwen Cloud
// authentication target.
let browserOrder: BrowserCookieImportOrder = [
.chrome,
.brave,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Update failure guidance to include Brave

When a user has Qwen Cloud only in Brave but its cookies cannot be imported, this new Brave attempt still surfaces QwenCloudSettingsError.missingCookie, whose message in QwenCloudSettingsReader.swift:56-58 tells them to sign in with Chrome and grant Chrome Safe Storage access. That guidance is now incorrect for the newly supported path and can send affected Brave users back to Chrome instead of telling them to grant Brave Safe Storage access or sign in using either supported browser.

Useful? React with 👍 / 👎.

]
#else
let browserOrder: BrowserCookieImportOrder? = nil
#endif
Expand Down Expand Up @@ -88,7 +100,11 @@ struct QwenCloudWebFetchStrategy: ProviderFetchStrategy {
private static let log = CodexBarLog.logger("qwen-cloud")

#if os(macOS)
static let browserOrder: BrowserCookieImportOrder = [.chrome]
/// Mirrors the descriptor's browserOrder above. Keep them in sync.
static let browserOrder: BrowserCookieImportOrder = [
.chrome,
.brave,
]
#endif

let id: String = "qwen-cloud.web"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,15 @@ public enum QwenCloudSettingsError: LocalizedError, Sendable {
public var errorDescription: String? {
switch self {
case let .missingCookie(details):
// Qwen Cloud cookie import now probes Chrome *and* Brave (per the
// QwenCloudProviderDescriptor.browserOrder override). The recovery
// message must name both supported browsers and their respective
// Safe Storage entries, otherwise a Brave-only user would be told
// to sign in with Chrome and the fix would be invisible.
let base = "No Qwen Cloud session cookies found in browsers. " +
"Sign in to Qwen Cloud in Chrome, allow CodexBar to access Chrome Safe Storage in Keychain Access, " +
"Sign in to Qwen Cloud in Chrome or Brave, " +
"allow CodexBar to access the corresponding Safe Storage in Keychain Access " +
"(Chrome Safe Storage and/or Brave Safe Storage), " +
"or paste a manual Cookie header."
guard let details, !details.isEmpty else { return base }
return "\(base) \(details)"
Expand Down
46 changes: 44 additions & 2 deletions Tests/CodexBarTests/QwenCloudProviderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,44 @@ private func qwenCloudFixture(_ name: String) throws -> Data {
}

struct QwenCloudSettingsReaderTests {
@Test
func `missing cookie error mentions both supported browsers and their safe storage`() {
// The cookie import now probes Chrome and Brave (per
// QwenCloudProviderDescriptor.browserOrder). The recovery message
// must name both, otherwise a Brave-only user gets directed at
// Chrome and never finds the right path.
let error = QwenCloudSettingsError.missingCookie()
let message = error.errorDescription ?? ""

#expect(message.contains("Chrome"))
#expect(message.contains("Brave"))
#expect(message.contains("Safe Storage"))
#expect(message.contains("manual Cookie header"))
#expect(message.contains("Keychain Access"))
}

@Test
func `missing cookie error appends non-empty details`() {
let error = QwenCloudSettingsError.missingCookie(
details: "Chrome Safe Storage keychain denied")
let message = error.errorDescription ?? ""

#expect(message.contains("Chrome"))
#expect(message.contains("Brave"))
#expect(message.contains("Chrome Safe Storage keychain denied"))
}

@Test
func `missing cookie error omits empty details`() {
let error = QwenCloudSettingsError.missingCookie(details: "")
let message = error.errorDescription ?? ""

#expect(message.contains("Chrome"))
#expect(message.contains("Brave"))
// No trailing whitespace from an empty details suffix.
#expect(!message.hasSuffix(" "))
}

@Test
func `cookie reads from environment`() {
let cookie = QwenCloudSettingsReader.cookieHeader(environment: [
Expand Down Expand Up @@ -103,8 +141,12 @@ struct QwenCloudUsageSnapshotTests {
#expect(metadata.sessionLabel == "5-hour")
#expect(metadata.weeklyLabel == "Weekly")
#if os(macOS)
#expect(metadata.browserCookieOrder == [.chrome])
#expect(QwenCloudWebFetchStrategy.browserOrder == [.chrome])
let expectedOrder: BrowserCookieImportOrder = [
.chrome,
.brave,
]
#expect(metadata.browserCookieOrder == expectedOrder)
#expect(QwenCloudWebFetchStrategy.browserOrder == expectedOrder)
#else
#expect(metadata.browserCookieOrder == nil)
#endif
Expand Down
100 changes: 100 additions & 0 deletions docs/qwen-cloud-proof/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Qwen Cloud browser-cookie import — real behavior proof

Captured 2026-08-22 against the live Qwen Cloud API via the modified CodexBar
binary (`/Applications/CodexBar-QwenFix.app`, debug build of branch
`diagnose/qwen-cloud-cookie-error`).

## Before the fix (commit `529cc6c24` "Keep Qwen imports Chrome-only")

```text
$ CodexBarCLI usage --provider qwen-cloud --format text --no-color
[error] No Qwen Cloud session cookies found in browsers. Sign in to Qwen Cloud
in Chrome, allow CodexBar to access Chrome Safe Storage in Keychain
Access, or paste a manual Cookie header.
```

The user is signed in to Qwen Cloud in **Brave** (not Chrome). The cookie
import never probed Brave, so the importer returned no session even though
`login_qwencloud_ticket` was present in
`~/Library/Application Support/BraveSoftware/Brave-Browser/Default/Cookies`.

## After the fix (branch `diagnose/qwen-cloud-cookie-error`, PR #3147)

`browserOrder` is now `[.chrome, .brave]` (per `AGENTS.md` L48 — "default
Chrome-only when possible to avoid other browser prompts; override via
browser list when needed"). Once the user granted macOS Keychain access to
the modified binary, the same CLI call returns real usage data:

```text
$ CodexBarCLI usage --provider qwen-cloud --format text --no-color
== Qwen Cloud (web) ==
Weekly: 20% left [==----------]
Resets in 2d 2h
Plan: Pro
```

```json
{
"source": "web",
"provider": "qwencloud",
"usage": {
"identity": {
"providerID": "qwencloud",
"loginMethod": "Pro"
},
"secondary": {
"usedPercent": 79.97,
"windowMinutes": 10080,
"resetsAt": "2026-08-25T01:21:00Z",
"resetDescription": "31,987.03 / 40,000 credits used"
},
"updatedAt": "2026-08-22T23:11:11Z"
}
}
```

## How the proof was obtained

1. `swift build` from the worktree at `/tmp/codexbar-main` succeeded.
2. `swift test --filter QwenCloudProviderTests` — 32/32 tests passed across 7 suites.
3. `./Scripts/package_app.sh debug` — packaged `CodexBar.app` (ad-hoc signed).
4. Installed to `/Applications/CodexBar-QwenFix.app` (no quarantine).
5. First refresh via the GUI menu bar — the macOS Keychain dialog appeared for `Brave Safe Storage`; "Always Allow" granted permanent ACL access.
6. CLI runs thereafter (with `CODEXBAR_ALLOW_BROWSER_COOKIE_IMPORT=1`) return the same real usage data shown above.

## What changed in the code

```diff
--- a/Sources/CodexBarCore/Providers/QwenCloud/QwenCloudProviderDescriptor.swift
+++ b/Sources/CodexBarCore/Providers/QwenCloud/QwenCloudProviderDescriptor.swift
- let browserOrder: BrowserCookieImportOrder = [.chrome]
+ // Default the import to Chrome + Brave. The full 7-browser list
+ // (chromeBeta, edge, arc, firefox, safari) was deliberately trimmed to
+ // avoid unsolicited Keychain / browser-store access prompts on automatic
+ // refreshes — the repository's prompt-avoidance policy. Brave is kept
+ // because it shares the same Chromium Safe Storage format and many
+ // CodexBar users authenticate Qwen Cloud in Brave.
+ let browserOrder: BrowserCookieImportOrder = [
+ .chrome,
+ .brave,
+ ]
```

```diff
--- a/Tests/CodexBarTests/QwenCloudProviderTests.swift
+++ b/Tests/CodexBarTests/QwenCloudProviderTests.swift
- #expect(metadata.browserCookieOrder == [.chrome])
- #expect(QwenCloudWebFetchStrategy.browserOrder == [.chrome])
+ let expectedOrder: BrowserCookieImportOrder = [
+ .chrome,
+ .brave,
+ ]
+ #expect(metadata.browserCookieOrder == expectedOrder)
+ #expect(QwenCloudWebFetchStrategy.browserOrder == expectedOrder)
```

## Redaction notes

- `loginMethod: "Pro"` is the plan tier only — no account identifier, no email, no user ID, no cookie values, no Keychain contents are reproduced here.
- The 79.97% weekly figure is a snapshot, not a real customer number.
- The cookie value column in the Brave SQLite cookie store is empty (the value is stored in the `encrypted_value` BLOB and only readable after decrypting with the user's Brave Safe Storage key); no decrypted cookie contents appear anywhere in this proof.