Skip to content

Add ChatGPT/Codex subscription tier (loopback proxy)#7401

Closed
Git-on-my-level wants to merge 2 commits into
BasedHardware:mainfrom
Git-on-my-level:codex/chatgpt-subscription-support
Closed

Add ChatGPT/Codex subscription tier (loopback proxy)#7401
Git-on-my-level wants to merge 2 commits into
BasedHardware:mainfrom
Git-on-my-level:codex/chatgpt-subscription-support

Conversation

@Git-on-my-level

@Git-on-my-level Git-on-my-level commented May 20, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Adds ChatGPT/Codex subscription enrollment on the backend (/v1/users/me/chatgpt-active) with LLM quota bypass (separate from four-key BYOK; transcription gates unchanged).
  • Ships a localhost OpenAI-compatible Codex proxy (desktop/codex-proxy) and desktop Settings UX to sign in via codex login, enroll, and route proactive LLM workloads through the proxy.
  • Adds local memory wiki + FTS5 search when ChatGPT tier is active (replaces vector embedding search for task/memory assistants).

Test plan

  • backend/tests/unit/test_chatgpt_enrollment.py
  • cd desktop/codex-proxy && cargo build --release
  • Desktop: Settings → Sign in with ChatGPT → complete Terminal codex login → verify proxy health and subscription shows unlimited LLM
  • Proactive capture / task extraction works with ChatGPT tier (no Gemini proxy calls for enrolled users)

Made with Cursor

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bcdc7bbed2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread backend/routers/users.py
Comment on lines +826 to +830
raise HTTPException(
status_code=400,
detail='Invalid fingerprint: expected lowercase hex SHA-256 (64 chars)',
)
users_db.set_chatgpt_active(uid, data.fingerprint)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P0 Badge Verify subscription ownership before setting chatgpt.active

This endpoint grants ChatGPT enrollment after only a format check on fingerprint, so any authenticated user can POST an arbitrary 64-hex string and flip chatgpt.active to true. Because downstream gating (is_chatgpt_active) is what unlocks unlimited subscription/quota paths, this creates a direct paid-feature bypass without proving the caller actually has a ChatGPT/Codex subscription.

Useful? React with 👍 / 👎.

Comment on lines +211 to +215
"model": if requested_model_hint.trim().is_empty() { Value::Null } else { Value::String(requested_model_hint.clone()) },
"choices": [{
"index": 0,
"message": { "role": "assistant", "content": assistant_text },
"logprobs": null,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve tool-call outputs in proxy chat completion responses

The proxy response synthesized here always returns only assistant text content and never includes message.tool_calls, even when the upstream Codex stream represents tool-calling output. CodexLLMClient.performGeminiCompatibleToolRound depends on tool_calls to drive Task/Insight tool loops, so in ChatGPT mode those flows can terminate early or fail on the first required-tool round because no executable tool call is propagated.

Useful? React with 👍 / 👎.

Comment thread backend/database/users.py
Comment on lines +232 to +236
if isinstance(last_seen, datetime):
age = (datetime.now(timezone.utc) - last_seen).total_seconds()
else:
return False
return age <= BYOK_HEARTBEAT_TTL_SECONDS

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 Refresh ChatGPT enrollment before TTL-based deactivation

is_chatgpt_active expires enrollment strictly by a 7-day TTL on last_seen_at, but desktop enrollment currently updates that timestamp only during explicit connect flow (not on normal app startup/use). As a result, active users can silently drop out of ChatGPT-unlimited status after seven days unless they manually reconnect, which causes unexpected quota/paywall regressions.

Useful? React with 👍 / 👎.

@greptile-apps

greptile-apps Bot commented May 20, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces a ChatGPT/Codex subscription tier that lets desktop users route proactive LLM workloads through a local loopback proxy (desktop/codex-proxy) instead of Gemini. Enrollment is stored as a SHA-256 fingerprint of the Codex account_id in Firestore, and a per-request header (X-ChatGPT-Fingerprint) is verified before granting quota and trial-paywall bypasses.

  • Backend: Adds ChatGPTMiddleware to extract the fingerprint per-request, two new endpoints (POST/DELETE /v1/users/me/chatgpt-active), and bypass hooks in enforce_chat_quota, _is_trial_expired_uncached, and get_user_subscription_endpoint.
  • Proxy: Adds a Rust axum-based loopback that translates OpenAI Chat Completions requests into Codex Responses API calls, with token refresh via OAuth.
  • Desktop: GeminiClient gains a .codexOpenAICompatible transport that delegates to CodexLLMClient, routing all proactive assistant workloads through the loopback proxy when CodexAuthService.isActive. MemoryWikiStorage adds FTS5-based local search to replace vector-embedding deduplication for ChatGPT-tier users.

Confidence Score: 3/5

The enrollment endpoint accepts any valid SHA-256 hex as a legitimate Codex fingerprint without verifying it against OpenAI, meaning any authenticated Omi user can self-enroll and permanently bypass both the 3-day desktop trial and monthly LLM quota.

The fingerprint stored at enrollment is never validated against a live ChatGPT session — only its format (64-char hex) is checked. Because is_chatgpt_active reads solely from Firestore (active flag + TTL), a user who POSTs an arbitrary SHA-256 string gets an unconditional trial-paywall and chat-quota bypass for at least a week. Additionally, MemorySearchMode.current defaults to .localWiki for all desktop users lacking an explicit vector preference key, silently disabling vector-embedding deduplication across the entire user base, not just ChatGPT enrollees.

backend/routers/users.py and backend/utils/chatgpt.py (enrollment + bypass logic) and desktop/macos/Desktop/Sources/MemoryWikiStorage.swift (default search-mode fallback) warrant the closest review before merge.

Important Files Changed

Filename Overview
backend/utils/chatgpt.py New per-request ChatGPT fingerprint middleware and bypass helper; chatgpt_request_grants_bypass issues two Firestore reads per call (one inside is_chatgpt_active, one for get_chatgpt_state).
backend/routers/users.py Adds ChatGPT enrollment endpoints and injects bypass into subscription/quota responses; fingerprint accepted as any valid SHA-256 hex without OpenAI-side verification.
backend/utils/subscription.py ChatGPT bypass added to trial-paywall and enforce_chat_quota; trial bypass uses Firestore-only is_chatgpt_active (no request-level header escape hatch, consistent with BYOK path).
backend/database/users.py Adds ChatGPT Firestore state helpers (get/set/touch/clear); reuses BYOK_HEARTBEAT_TTL_SECONDS constant semantically tied to a different feature.
desktop/codex-proxy/src/main.rs New Rust axum loopback proxy translating OpenAI Chat Completions to Codex Responses API with OAuth refresh; codex_body_to_chat_completion is dead production code never reached by invoke_codex.
desktop/macos/Desktop/Sources/MemoryWikiStorage.swift New GRDB-backed FTS5 wiki storage; MemorySearchMode.current falls back to "local_wiki" for all users without an explicit "vector" preference, disabling vector-embedding deduplication by default.
desktop/macos/Desktop/Sources/CodexProxyService.swift Manages lifecycle of the loopback proxy process; stderr is silenced, making startup failures opaque beyond a generic health-check timeout message.
desktop/macos/Desktop/Sources/CodexEnrollmentCoordinator.swift Orchestrates sign-in via npx @openai/codex login, polls for auth.json, starts proxy, then POSTs fingerprint to backend; static connectInFlight guard prevents parallel enrollments.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Desktop as Desktop (Swift)
    participant Proxy as codex-proxy (Rust :10531)
    participant Omi as Omi Backend (FastAPI)
    participant Firestore as Firestore
    participant Codex as ChatGPT Codex API

    Note over Desktop: User clicks Sign in with ChatGPT
    Desktop->>Desktop: "launchCodexLogin terminal npx @openai/codex login"
    Desktop->>Desktop: Poll ~/.codex/auth.json (2s interval, 120s timeout)
    Desktop->>Proxy: Start omi-codex-proxy process
    Proxy->>Proxy: Load auth.json, bind :10531
    Desktop->>Proxy: GET /health (poll 30x100ms)
    Proxy-->>Desktop: 200 ok
    Desktop->>Omi: POST /v1/users/me/chatgpt-active fingerprint SHA256(account_id)
    Omi->>Firestore: set_chatgpt_active(uid, fingerprint)
    Omi-->>Desktop: active true

    Note over Desktop: Per-request (proactive assistants)
    Desktop->>Proxy: POST /v1/chat/completions messages model
    Proxy->>Proxy: codex_payload_from_openai_chat()
    Proxy->>Codex: POST /backend-api/codex/responses SSE
    Codex-->>Proxy: text/event-stream chunks
    Proxy->>Proxy: collect_text_from_codex_sse()
    Proxy-->>Desktop: choices message content

    Note over Desktop: Per-request (backend API calls)
    Desktop->>Omi: Any API call + X-ChatGPT-Fingerprint header
    Omi->>Omi: ChatGPTMiddleware sets ContextVar
    Omi->>Firestore: is_chatgpt_active(uid) read 1
    Omi->>Firestore: get_chatgpt_state(uid) read 2
    Omi->>Omi: verify fingerprint matches, refresh heartbeat
    Omi-->>Desktop: bypass quota / trial paywall
Loading
%%{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"}}}%%
sequenceDiagram
    participant Desktop as Desktop (Swift)
    participant Proxy as codex-proxy (Rust :10531)
    participant Omi as Omi Backend (FastAPI)
    participant Firestore as Firestore
    participant Codex as ChatGPT Codex API

    Note over Desktop: User clicks Sign in with ChatGPT
    Desktop->>Desktop: "launchCodexLogin terminal npx @openai/codex login"
    Desktop->>Desktop: Poll ~/.codex/auth.json (2s interval, 120s timeout)
    Desktop->>Proxy: Start omi-codex-proxy process
    Proxy->>Proxy: Load auth.json, bind :10531
    Desktop->>Proxy: GET /health (poll 30x100ms)
    Proxy-->>Desktop: 200 ok
    Desktop->>Omi: POST /v1/users/me/chatgpt-active fingerprint SHA256(account_id)
    Omi->>Firestore: set_chatgpt_active(uid, fingerprint)
    Omi-->>Desktop: active true

    Note over Desktop: Per-request (proactive assistants)
    Desktop->>Proxy: POST /v1/chat/completions messages model
    Proxy->>Proxy: codex_payload_from_openai_chat()
    Proxy->>Codex: POST /backend-api/codex/responses SSE
    Codex-->>Proxy: text/event-stream chunks
    Proxy->>Proxy: collect_text_from_codex_sse()
    Proxy-->>Desktop: choices message content

    Note over Desktop: Per-request (backend API calls)
    Desktop->>Omi: Any API call + X-ChatGPT-Fingerprint header
    Omi->>Omi: ChatGPTMiddleware sets ContextVar
    Omi->>Firestore: is_chatgpt_active(uid) read 1
    Omi->>Firestore: get_chatgpt_state(uid) read 2
    Omi->>Omi: verify fingerprint matches, refresh heartbeat
    Omi-->>Desktop: bypass quota / trial paywall
Loading

Reviews (2): Last reviewed commit: "Fix ChatGPT tier security and desktop Co..." | Re-trigger Greptile

Comment on lines +171 to +172
let raw = UserDefaults.standard.string(forKey: "memory_search_mode") ?? "local_wiki"
return raw == "vector" ? .vectorEmbeddings : .localWiki

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 The fallback value "local_wiki" causes all desktop users (regardless of ChatGPT tier) to use the local wiki search by default. Any user without a stored memory_search_mode preference gets .localWiki, which disables vector embedding search system-wide. The PR description states local wiki should only activate when ChatGPT tier is active, so non-ChatGPT users would lose vector-search deduplication in both TaskAssistant and MemoryAssistant.

Suggested change
let raw = UserDefaults.standard.string(forKey: "memory_search_mode") ?? "local_wiki"
return raw == "vector" ? .vectorEmbeddings : .localWiki
let raw = UserDefaults.standard.string(forKey: "memory_search_mode") ?? "vector"
return raw == "vector" ? .vectorEmbeddings : .localWiki

Comment thread backend/routers/users.py
Comment on lines +820 to +832
@router.post('/v1/users/me/chatgpt-active', tags=['v1'])
def activate_chatgpt_endpoint(
data: ChatGPTActivateRequest, uid: str = Depends(auth.get_current_user_uid_no_byok_validation)
):
"""Enroll ChatGPT / Codex subscription tier (LLM workloads only; no provider keys stored)."""
if not _SHA256_HEX_RE.match(data.fingerprint):
raise HTTPException(
status_code=400,
detail='Invalid fingerprint: expected lowercase hex SHA-256 (64 chars)',
)
users_db.set_chatgpt_active(uid, data.fingerprint)
clear_trial_paywall_cache(uid)
return {"active": True}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 security No server-side validation of ChatGPT subscription

The activation endpoint accepts any 64-char hex string as a valid enrollment fingerprint. The fingerprint is stored in Firestore, but is_chatgpt_active (and therefore enforce_chat_quota) never re-verifies it against OpenAI — it only checks that active=True and the Firestore timestamp is fresh. Any authenticated Omi user can POST an arbitrary SHA-256 hex to permanently bypass the 3-day trial paywall and monthly LLM quota without ever having a ChatGPT subscription. Unlike BYOK, there is no per-request proof-of-possession; once enrolled, the bypass is unconditional.

Comment thread backend/database/users.py
Comment on lines +232 to +239
if isinstance(last_seen, datetime):
age = (datetime.now(timezone.utc) - last_seen).total_seconds()
else:
return False
return age <= BYOK_HEARTBEAT_TTL_SECONDS


def set_chatgpt_active(uid: str, fingerprint: str):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 The ChatGPT TTL reuses BYOK_HEARTBEAT_TTL_SECONDS, a constant whose name and comment are semantically tied to the BYOK feature. If the two TTLs ever diverge, this shared constant would be the wrong one to change. A dedicated constant makes the intent explicit.

Suggested change
if isinstance(last_seen, datetime):
age = (datetime.now(timezone.utc) - last_seen).total_seconds()
else:
return False
return age <= BYOK_HEARTBEAT_TTL_SECONDS
def set_chatgpt_active(uid: str, fingerprint: str):
if isinstance(last_seen, datetime):
age = (datetime.now(timezone.utc) - last_seen).total_seconds()
else:
return False
return age <= CHATGPT_HEARTBEAT_TTL_SECONDS
def set_chatgpt_active(uid: str, fingerprint: str):

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +567 to +606
fn codex_body_to_chat_completion(model_fallback: &str, bytes: &[u8]) -> Result<Value, String> {
let v: Value = serde_json::from_slice(bytes).map_err(|e| format!("upstream json: {e}"))?;

if v.get("choices").is_some() {
let mut enriched = v;
if enriched.get("id").and_then(Value::as_str).is_none()
|| enriched.get("id") == Some(&Value::Null)
{
enriched["id"] = Value::String(new_chat_completion_id());
}
if enriched.get("object").and_then(Value::as_str).is_none()
|| enriched.get("object") == Some(&Value::Null)
{
enriched["object"] = Value::from("chat.completion");
}
if enriched.get("created").and_then(Value::as_i64).is_none()
|| enriched.get("created") == Some(&Value::Null)
{
enriched["created"] = Value::Number(unix_secs().into());
}
Ok(enriched)
} else {
let text = extract_assistant_text(&v)
.ok_or_else(|| serde_json::to_string(&v).unwrap_or_else(|_| "(unprintable)".into()))?;
let model = chat_model_choice(&v, model_fallback)?;
Ok(json!({
"id": new_chat_completion_id(),
"object": "chat.completion",
"created": unix_secs(),
"model": model,
"choices": [{
"index": 0,
"message": { "role": "assistant", "content": text},
"logprobs": null,
"finish_reason": infer_finish_reason(&v),
}],
"usage": v.get("usage").cloned().unwrap_or(Value::Null),
}))
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 codex_body_to_chat_completion is dead production code

invoke_codex assembles the response inline using collect_text_from_codex_sse and never calls this function. It is referenced only by the maps_responses_like_output_message unit test. Keeping it creates a diverging code path that can mislead future contributors into thinking the proxy has a non-SSE fallback mode.

Comment on lines +74 to +75
proc.standardOutput = FileHandle.nullDevice
proc.standardError = FileHandle.nullDevice

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Proxy stderr silenced — startup failures are undiagnosable

proc.standardError = FileHandle.nullDevice discards all error output from the proxy process. If the proxy crashes on startup (missing auth file, port conflict, bad token format), the only signal the Swift side sees is a health-check timeout with the generic message "Codex proxy failed to start". Routing stderr to a pipe would allow the error message to be surfaced in lastError.

Git-on-my-level and others added 2 commits June 18, 2026 23:55
Route proactive LLM through a localhost Codex proxy using ~/.codex auth,
add Settings enrollment UX, local memory wiki + FTS for search, backend
tier activation, and bundle the proxy in run.sh. Adapted for upstream main
without hybrid local-daemon dependencies.

Co-authored-by: Cursor <cursoragent@cursor.com>
Require X-ChatGPT-Fingerprint on requests for quota and subscription bypass,
refresh enrollment heartbeat from desktop launch and throttled server updates,
resolve Codex transport at call time in GeminiClient, label wiki search hits
distinctly from tasks, and include memory id in wiki slugs to avoid collisions.
@Git-on-my-level Git-on-my-level force-pushed the codex/chatgpt-subscription-support branch from 63fde7b to 2cdc24b Compare June 18, 2026 16:55
@Git-on-my-level Git-on-my-level marked this pull request as ready for review June 18, 2026 17:42

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

17 issues found across 27 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="desktop/macos/Desktop/Sources/ProactiveAssistants/Assistants/MemoryExtraction/MemoryAssistant.swift">

<violation number="1" location="desktop/macos/Desktop/Sources/ProactiveAssistants/Assistants/MemoryExtraction/MemoryAssistant.swift:258">
P2: `try?` silently suppresses wiki indexing failures, making missing memory-search entries hard to detect and debug. Log the error (or surface it) instead of discarding it.</violation>
</file>

<file name="desktop/macos/run.sh">

<violation number="1" location="desktop/macos/run.sh:474">
P1: Codex proxy path is incorrect in run.sh, so the new proxy is never built or bundled.</violation>

<violation number="2" location="desktop/macos/run.sh:479">
P2: New helper binary is executable but never individually codesigned. Unsigned nested executables are commonly blocked at runtime, so Codex proxy launch can fail even when app signing succeeds.</violation>
</file>

<file name="desktop/macos/Desktop/Tests/CodexAuthServiceTests.swift">

<violation number="1" location="desktop/macos/Desktop/Tests/CodexAuthServiceTests.swift:31">
P2: `testMemorySearchModeDefaultsToWikiWhenCodexEnrolled` does not actually validate the enrollment-to-wiki path — no auth file is configured, so `CodexAuthService.isActive` is always false and `MemorySearchMode.current` never enters the `isActive` branch. The assertion is vacuously true via the UserDefaults default.</violation>
</file>

<file name="desktop/macos/Desktop/Sources/CodexProxyService.swift">

<violation number="1" location="desktop/macos/Desktop/Sources/CodexProxyService.swift:154">
P2: Repo-relative executable lookup is one directory too deep and misses the local proxy build output. In dev setups without a bundled binary/PATH entry, `ensureRunning()` fails with “binary not found.”</violation>
</file>

<file name="desktop/macos/Desktop/Sources/MainWindow/Pages/SettingsPage.swift">

<violation number="1" location="desktop/macos/Desktop/Sources/MainWindow/Pages/SettingsPage.swift:5430">
P2: Settings copy promises main chat is routed through ChatGPT, but current chat flow is still pi-mono/ACP. This misleads users about what enrollment changes.</violation>
</file>

<file name="backend/utils/subscription.py">

<violation number="1" location="backend/utils/subscription.py:126">
P1: Trial/paywall bypass is granted from persisted ChatGPT enrollment alone, without requiring the per-request fingerprint proof used elsewhere.</violation>

<violation number="2" location="backend/utils/subscription.py:625">
P0: Chat quota bypass is grantable via self-enrolled fingerprint, not a verified subscription entitlement. This allows free users to bypass quota limits by posting an arbitrary fingerprint and replaying it in request headers.</violation>
</file>

<file name="desktop/macos/Desktop/Sources/CodexAuthService.swift">

<violation number="1" location="desktop/macos/Desktop/Sources/CodexAuthService.swift:107">
P2: Guard order triggers auth-file disk reads even when user is not enrolled. Check `isEnrolled` first to skip file I/O on the common non-enrolled path.</violation>
</file>

<file name="desktop/macos/Desktop/Sources/ProactiveAssistants/Core/GeminiClient.swift">

<violation number="1" location="desktop/macos/Desktop/Sources/ProactiveAssistants/Core/GeminiClient.swift:306">
P2: OCR fallback leaks raw Codex client errors instead of mapping them to GeminiClientError. This can skip transient retry handling and inconsistent user-facing errors.</violation>
</file>

<file name="desktop/macos/Desktop/Sources/OmiApp.swift">

<violation number="1" location="desktop/macos/Desktop/Sources/OmiApp.swift:114">
P2: `try?` suppresses ChatGPT activation errors at launch, so backend enrollment sync can fail silently. Catch and log the error (or trigger retry) so users don’t remain in an unsynced tier state without diagnostics.</violation>
</file>

<file name="desktop/macos/Desktop/Sources/MemoryWikiStorage.swift">

<violation number="1" location="desktop/macos/Desktop/Sources/MemoryWikiStorage.swift:44">
P1: ensureDB can reuse a stale DatabasePool across sign-out/sign-in. This risks cross-account reads/writes in memory wiki data.</violation>

<violation number="2" location="desktop/macos/Desktop/Sources/MemoryWikiStorage.swift:171">
P2: The default search mode is inverted for non-Codex users. Falling back to `local_wiki` globally disables vector embedding search unless a hidden override is manually set.</violation>
</file>

<file name="backend/utils/chatgpt.py">

<violation number="1" location="backend/utils/chatgpt.py:47">
P2: Bypass validation does two Firestore reads per request when one is enough. This adds avoidable latency/cost on chat and quota paths for enrolled desktop users.</violation>
</file>

<file name="desktop/codex-proxy/src/main.rs">

<violation number="1" location="desktop/codex-proxy/src/main.rs:211">
P2: Response `model` is set to null when request omits `model`, breaking OpenAI-compatible schema expectations. Use the effective upstream model fallback instead of `null`.</violation>
</file>

<file name="backend/routers/users.py">

<violation number="1" location="backend/routers/users.py:931">
P1: ChatGPT enrollment trusts a self-asserted fingerprint with no server-side proof of subscription ownership. This lets authenticated users mint their own bypass token and get unlimited chat quota.</violation>
</file>

<file name="desktop/macos/Desktop/Sources/CodexLLMClient.swift">

<violation number="1" location="desktop/macos/Desktop/Sources/CodexLLMClient.swift:67">
P2: `postJSON` decodes JSON before checking status code, which can misclassify HTTP failures and bypass downstream fallback handling.</violation>
</file>

Tip: instead of fixing issues one by one fix them all with cubic
Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.

Re-trigger cubic


# BYOK users pay their own LLM provider — no Omi-side cost to cap.
# ChatGPT/Codex tier: bypass only when this request proves Codex enrollment (header).
if chatgpt_request_grants_bypass(uid):

@cubic-dev-ai cubic-dev-ai Bot Jun 18, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P0: Chat quota bypass is grantable via self-enrolled fingerprint, not a verified subscription entitlement. This allows free users to bypass quota limits by posting an arbitrary fingerprint and replaying it in request headers.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/utils/subscription.py, line 625:

<comment>Chat quota bypass is grantable via self-enrolled fingerprint, not a verified subscription entitlement. This allows free users to bypass quota limits by posting an arbitrary fingerprint and replaying it in request headers.</comment>

<file context>
@@ -613,6 +621,10 @@ def enforce_chat_quota(uid: str, platform: Optional[str] = None) -> None:
 
     # BYOK users pay their own LLM provider — no Omi-side cost to cap.
+    # ChatGPT/Codex tier: bypass only when this request proves Codex enrollment (header).
+    if chatgpt_request_grants_bypass(uid):
+        return
+
</file context>
Fix with cubic

}

private func ensureDB() async throws -> DatabasePool {
if let dbQueue { return dbQueue }

@cubic-dev-ai cubic-dev-ai Bot Jun 18, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: ensureDB can reuse a stale DatabasePool across sign-out/sign-in. This risks cross-account reads/writes in memory wiki data.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At desktop/macos/Desktop/Sources/MemoryWikiStorage.swift, line 44:

<comment>ensureDB can reuse a stale DatabasePool across sign-out/sign-in. This risks cross-account reads/writes in memory wiki data.</comment>

<file context>
@@ -0,0 +1,178 @@
+  }
+
+  private func ensureDB() async throws -> DatabasePool {
+    if let dbQueue { return dbQueue }
+    try await RewindDatabase.shared.initialize()
+    guard let queue = await RewindDatabase.shared.getDatabaseQueue() else {
</file context>
Fix with cubic

Comment thread desktop/macos/run.sh
fi

substep "Building Codex proxy (omi-codex-proxy)"
CODEX_PROXY_DIR="$(dirname "$0")/codex-proxy"

@cubic-dev-ai cubic-dev-ai Bot Jun 18, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: Codex proxy path is incorrect in run.sh, so the new proxy is never built or bundled.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At desktop/macos/run.sh, line 474:

<comment>Codex proxy path is incorrect in run.sh, so the new proxy is never built or bundled.</comment>

<file context>
@@ -470,6 +470,17 @@ else
 fi
 
+substep "Building Codex proxy (omi-codex-proxy)"
+CODEX_PROXY_DIR="$(dirname "$0")/codex-proxy"
+if [ -d "$CODEX_PROXY_DIR" ]; then
+    (cd "$CODEX_PROXY_DIR" && cargo build --release --quiet)
</file context>
Suggested change
CODEX_PROXY_DIR="$(dirname "$0")/codex-proxy"
CODEX_PROXY_DIR="$(dirname "$0")/../codex-proxy"
Fix with cubic

Comment thread backend/routers/users.py
status_code=400,
detail='Invalid fingerprint: expected lowercase hex SHA-256 (64 chars)',
)
users_db.set_chatgpt_active(uid, data.fingerprint)

@cubic-dev-ai cubic-dev-ai Bot Jun 18, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: ChatGPT enrollment trusts a self-asserted fingerprint with no server-side proof of subscription ownership. This lets authenticated users mint their own bypass token and get unlimited chat quota.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/routers/users.py, line 931:

<comment>ChatGPT enrollment trusts a self-asserted fingerprint with no server-side proof of subscription ownership. This lets authenticated users mint their own bypass token and get unlimited chat quota.</comment>

<file context>
@@ -913,6 +914,47 @@ def deactivate_byok_endpoint(uid: str = Depends(auth.get_current_user_uid_no_byo
+            status_code=400,
+            detail='Invalid fingerprint: expected lowercase hex SHA-256 (64 chars)',
+        )
+    users_db.set_chatgpt_active(uid, data.fingerprint)
+    clear_trial_paywall_cache(uid)
+    return {"active": True}
</file context>
Fix with cubic

return False
if users_db.is_byok_active(uid):
return False
if users_db.is_chatgpt_active(uid):

@cubic-dev-ai cubic-dev-ai Bot Jun 18, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: Trial/paywall bypass is granted from persisted ChatGPT enrollment alone, without requiring the per-request fingerprint proof used elsewhere.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/utils/subscription.py, line 126:

<comment>Trial/paywall bypass is granted from persisted ChatGPT enrollment alone, without requiring the per-request fingerprint proof used elsewhere.</comment>

<file context>
@@ -122,6 +123,8 @@ def _is_trial_expired_uncached(uid: str) -> bool:
             return False
         if users_db.is_byok_active(uid):
             return False
+        if users_db.is_chatgpt_active(uid):
+            return False
         user_record = firebase_auth.get_user(uid)
</file context>
Fix with cubic

Task {
await CodexProxyService.shared.ensureRunning()
if let fingerprint = CodexAuthService.enrollmentFingerprintIfActive() {
try? await APIClient.shared.activateChatGPT(fingerprint: fingerprint)

@cubic-dev-ai cubic-dev-ai Bot Jun 18, 2026

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: try? suppresses ChatGPT activation errors at launch, so backend enrollment sync can fail silently. Catch and log the error (or trigger retry) so users don’t remain in an unsynced tier state without diagnostics.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At desktop/macos/Desktop/Sources/OmiApp.swift, line 114:

<comment>`try?` suppresses ChatGPT activation errors at launch, so backend enrollment sync can fail silently. Catch and log the error (or trigger retry) so users don’t remain in an unsynced tier state without diagnostics.</comment>

<file context>
@@ -107,6 +107,14 @@ struct OMIApp: App {
+            Task {
+              await CodexProxyService.shared.ensureRunning()
+              if let fingerprint = CodexAuthService.enrollmentFingerprintIfActive() {
+                try? await APIClient.shared.activateChatGPT(fingerprint: fingerprint)
+              }
+            }
</file context>
Suggested change
try? await APIClient.shared.activateChatGPT(fingerprint: fingerprint)
do {
try await APIClient.shared.activateChatGPT(fingerprint: fingerprint)
} catch {
log("OmiApp: Failed to activate ChatGPT tier on launch: \(error.localizedDescription)")
}
Fix with cubic

if CodexAuthService.isActive {
return .localWiki
}
let raw = UserDefaults.standard.string(forKey: "memory_search_mode") ?? "local_wiki"

@cubic-dev-ai cubic-dev-ai Bot Jun 18, 2026

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: The default search mode is inverted for non-Codex users. Falling back to local_wiki globally disables vector embedding search unless a hidden override is manually set.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At desktop/macos/Desktop/Sources/MemoryWikiStorage.swift, line 171:

<comment>The default search mode is inverted for non-Codex users. Falling back to `local_wiki` globally disables vector embedding search unless a hidden override is manually set.</comment>

<file context>
@@ -0,0 +1,178 @@
+    if CodexAuthService.isActive {
+      return .localWiki
+    }
+    let raw = UserDefaults.standard.string(forKey: "memory_search_mode") ?? "local_wiki"
+    return raw == "vector" ? .vectorEmbeddings : .localWiki
+  }
</file context>
Suggested change
let raw = UserDefaults.standard.string(forKey: "memory_search_mode") ?? "local_wiki"
let raw = UserDefaults.standard.string(forKey: "memory_search_mode") ?? "vector"
Fix with cubic

Comment thread backend/utils/chatgpt.py
fp = _chatgpt_fp_ctx.get()
if not fp or not _SHA256_HEX_RE.match(fp):
return False
if not users_db.is_chatgpt_active(uid):

@cubic-dev-ai cubic-dev-ai Bot Jun 18, 2026

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: Bypass validation does two Firestore reads per request when one is enough. This adds avoidable latency/cost on chat and quota paths for enrolled desktop users.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/utils/chatgpt.py, line 47:

<comment>Bypass validation does two Firestore reads per request when one is enough. This adds avoidable latency/cost on chat and quota paths for enrolled desktop users.</comment>

<file context>
@@ -0,0 +1,74 @@
+    fp = _chatgpt_fp_ctx.get()
+    if not fp or not _SHA256_HEX_RE.match(fp):
+        return False
+    if not users_db.is_chatgpt_active(uid):
+        return False
+    state = users_db.get_chatgpt_state(uid)
</file context>
Fix with cubic

"id": new_chat_completion_id(),
"object": "chat.completion",
"created": unix_secs(),
"model": if requested_model_hint.trim().is_empty() { Value::Null } else { Value::String(requested_model_hint.clone()) },

@cubic-dev-ai cubic-dev-ai Bot Jun 18, 2026

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: Response model is set to null when request omits model, breaking OpenAI-compatible schema expectations. Use the effective upstream model fallback instead of null.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At desktop/codex-proxy/src/main.rs, line 211:

<comment>Response `model` is set to null when request omits `model`, breaking OpenAI-compatible schema expectations. Use the effective upstream model fallback instead of `null`.</comment>

<file context>
@@ -0,0 +1,799 @@
+            "id": new_chat_completion_id(),
+            "object": "chat.completion",
+            "created": unix_secs(),
+            "model": if requested_model_hint.trim().is_empty() { Value::Null } else { Value::String(requested_model_hint.clone()) },
+            "choices": [{
+              "index": 0,
</file context>
Fix with cubic

Comment on lines +67 to +73
guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] else {
throw ClientError.invalidResponse
}
guard (200..<300).contains(http.statusCode) else {
let body = String(data: data.prefix(512), encoding: .utf8) ?? ""
throw ClientError.httpFailure(status: http.statusCode, body: body)
}

@cubic-dev-ai cubic-dev-ai Bot Jun 18, 2026

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: postJSON decodes JSON before checking status code, which can misclassify HTTP failures and bypass downstream fallback handling.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At desktop/macos/Desktop/Sources/CodexLLMClient.swift, line 67:

<comment>`postJSON` decodes JSON before checking status code, which can misclassify HTTP failures and bypass downstream fallback handling.</comment>

<file context>
@@ -0,0 +1,401 @@
+    guard let http = response as? HTTPURLResponse else {
+      throw ClientError.invalidResponse
+    }
+    guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] else {
+      throw ClientError.invalidResponse
+    }
</file context>
Suggested change
guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] else {
throw ClientError.invalidResponse
}
guard (200..<300).contains(http.statusCode) else {
let body = String(data: data.prefix(512), encoding: .utf8) ?? ""
throw ClientError.httpFailure(status: http.statusCode, body: body)
}
guard (200..<300).contains(http.statusCode) else {
let body = String(data: data.prefix(512), encoding: .utf8) ?? ""
throw ClientError.httpFailure(status: http.statusCode, body: body)
}
guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] else {
throw ClientError.invalidResponse
}
Fix with cubic

@ThomsenDrake

Copy link
Copy Markdown
Collaborator

Closing as stale after confirmed stale-close triage. This PR was identified as high-confidence stale due to age plus one or more of: conflicts, draft/failing status, superseded work, or obsolete architecture. No merge was performed. If the work is still needed, please reopen with a fresh rebase or start from current main.

@github-actions

Copy link
Copy Markdown
Contributor

Hey @Git-on-my-level 👋

Thank you so much for taking the time to contribute to Omi! We truly appreciate you putting in the effort to submit this pull request.

After careful review, we've decided not to merge this particular PR. Please don't take this personally — we genuinely try to merge as many contributions as possible, but sometimes we have to make tough calls based on:

  • Project standards — Ensuring consistency across the codebase
  • User needs — Making sure changes align with what our users need
  • Code best practices — Maintaining code quality and maintainability
  • Project direction — Keeping aligned with our roadmap and vision

Your contribution is still valuable to us, and we'd love to see you contribute again in the future! If you'd like feedback on how to improve this PR or want to discuss alternative approaches, please don't hesitate to reach out.

Thank you for being part of the Omi community! 💜

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants