Skip to content

feat: add Gemini, Qwen Code, and Mistral Vibe providers#22

Open
jqueguiner wants to merge 3 commits into
ohong:mainfrom
jqueguiner:main
Open

feat: add Gemini, Qwen Code, and Mistral Vibe providers#22
jqueguiner wants to merge 3 commits into
ohong:mainfrom
jqueguiner:main

Conversation

@jqueguiner

@jqueguiner jqueguiner commented Mar 8, 2026

Copy link
Copy Markdown

Summary

  • Three new CLI provider modules (gemini.ts, qwen.ts, mistral.ts) that read local usage data from Gemini CLI, Qwen Code, and Mistral Vibe respectively
  • All providers run in parallel during straude push and merge into the daily usage pipeline via a now-variadic mergeEntries
  • Gemini tries gcusage CLI first, falls back to reading ~/.gemini/tmp/*/chats/ session files directly
  • Qwen reads JSONL from ~/.qwen/projects/*/chats/*.jsonl
  • Mistral reads ~/.vibe/logs/session/session_*/meta.json with cost estimation from pricing metadata
  • Model display names (Gemini Pro/Flash, Qwen Coder, Devstral, Codestral, etc.) added to ActivityCard, post-share, and recap utils
  • All 125 CLI tests pass

Provider JSON Output Formats

Gemini CLI (gemini.ts)

Reads from gcusage CLI or falls back to ~/.gemini/tmp/*/chats/session-*.json. Output format:

[
  {
    "date": "2026-03-04",
    "models": ["gemini-2.5-pro"],
    "input": 12500,
    "output": 3200,
    "thought": 800,
    "cache": 5000,
    "tool": 150
  }
]

Qwen Code (qwen.ts)

Reads JSONL from ~/.qwen/projects/*/chats/*.jsonl. Each line is a JSON object; assistant entries contain usageMetadata:

{
  "type": "assistant",
  "timestamp": "2026-03-04T10:30:00Z",
  "model": "coder-model",
  "usageMetadata": {
    "promptTokenCount": 8500,
    "candidatesTokenCount": 1200,
    "thoughtsTokenCount": 400,
    "totalTokenCount": 10100,
    "cachedContentTokenCount": 3000
  }
}

Aggregated output (internal, before merge):

[
  {
    "date": "2026-03-04",
    "models": ["coder-model"],
    "inputTokens": 8500,
    "outputTokens": 1600,
    "cacheCreationTokens": 0,
    "cacheReadTokens": 3000,
    "totalTokens": 10100,
    "costUSD": 0
  }
]

Mistral Vibe (mistral.ts)

Reads ~/.vibe/logs/session/session_*/meta.json. Each session directory contains a meta.json:

{
  "session_id": "abc123",
  "start_time": "2026-02-22T10:02:44Z",
  "config": {
    "active_model": "devstral-2"
  },
  "stats": {
    "session_prompt_tokens": 4874312,
    "session_completion_tokens": 13352,
    "input_price_per_million": 0.4,
    "output_price_per_million": 2.0,
    "session_cost": 4.3596708
  }
}

Aggregated output (internal, before merge):

[
  {
    "date": "2026-02-22",
    "models": ["devstral-2", "devstral-small"],
    "inputTokens": 34120186,
    "outputTokens": 93463,
    "cacheCreationTokens": 0,
    "cacheReadTokens": 0,
    "totalTokens": 34213649,
    "costUSD": 11.18
  }
]

Example CLI Output

$ straude push
Pushing usage for 2026-03-02 to 2026-03-08...
  2026-03-02:
    Cost: $238.45
    Tokens: 422.1M (input: 3.8M, output: 660k)
    Models: claude-opus-4-6, claude-haiku-4-5-20251001, claude-sonnet-4-6, coder-model
  2026-03-03:
    Cost: $393.65
    Tokens: 984.4M (input: 25.1M, output: 2.7M)
    Models: claude-opus-4-6, claude-sonnet-4-6, claude-haiku-4-5-20251001, gpt-5.3-codex
  2026-03-04:
    Cost: $162.66
    Tokens: 466.8M (input: 49.2M, output: 1.3M)
    Models: claude-opus-4-6, claude-haiku-4-5-20251001, claude-sonnet-4-6, gpt-5.3-codex, gemini-2.5-pro, coder-model
  2026-03-05:
    Cost: $538.22
    Tokens: 852.2M (input: 32.3M, output: 1.5M)
    Models: claude-opus-4-6, claude-sonnet-4-6, claude-haiku-4-5-20251001, gpt-5.3-codex, gemini-2.5-pro, coder-model
  2026-03-06:
    Cost: $340.95
    Tokens: 447.6M (input: 58k, output: 735k)
    Models: claude-opus-4-6, claude-haiku-4-5-20251001
  2026-03-07:
    Cost: $697.95
    Tokens: 986.5M (input: 48.6M, output: 2.0M)
    Models: claude-opus-4-6, claude-haiku-4-5-20251001, coder-model
  2026-03-08:
    Cost: $13.67
    Tokens: 47.9M (input: 29.2M, output: 172k)
    Models: claude-opus-4-6, claude-haiku-4-5-20251001, coder-model, devstral-small

Posted 2026-03-02: https://straude.com/post/370e23a7-0df1-40ee-8e80-d2696ec3f7be?edit=1
Posted 2026-03-03: https://straude.com/post/dec598e2-929c-4aad-9af5-173c5ed69051?edit=1
Updated 2026-03-04: https://straude.com/post/f33a4dc3-b697-4191-9d60-c87c6c2fb212?edit=1
Updated 2026-03-05: https://straude.com/post/2a8cc663-b40d-4fbf-aeab-89a20665bcc6?edit=1
Updated 2026-03-06: https://straude.com/post/3d31697f-8ecc-4cd7-8703-cb8372e7ab7b?edit=1
Updated 2026-03-07: https://straude.com/post/142940c6-79be-4bb9-acc0-0c9c8ba6e4c6?edit=1
Updated 2026-03-08: https://straude.com/post/2efcaadc-804c-48ce-a18c-88c31579939a?edit=1

Note: coder-model = Qwen Code, gemini-2.5-pro = Gemini CLI, devstral-small = Mistral Vibe. All three new providers merge seamlessly with existing Claude and Codex data.

Test plan

  • Verified Gemini data appears in dry-run output (gemini-2.5-pro)
  • Verified Qwen data appears in dry-run output (coder-model)
  • Verified Mistral data appears via direct reader test (devstral-2, devstral-small)
  • Ran full straude push — all providers merged correctly
  • All 125 CLI unit tests pass (npx vitest run)
  • Verify model names render correctly in web UI activity cards

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Added support for tracking usage data from Gemini, Qwen Code, and Mistral Vibe providers.
    • Enhanced model name display with improved recognition for multiple model variants (Pro, Flash, Max, Coder, Large, etc.).
  • Documentation

    • Updated changelog and decision documentation with new provider support details.

jqueguiner and others added 2 commits March 8, 2026 07:50
Add three new usage data providers that run in parallel during
`straude push`. Each reads local session files and fails silently
when not installed. Model display names added across the web UI.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
feat: add Gemini, Qwen Code, and Mistral Vibe providers
@vercel

vercel Bot commented Mar 8, 2026

Copy link
Copy Markdown

@jqueguiner is attempting to deploy a commit to the Pacific Systems Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Mar 8, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Introduces three new AI provider integrations (Gemini, Qwen, Mistral) with data retrieval and parsing modules. Updates the CLI usage aggregation system to merge multiple provider sources. Expands web UI model display name mappings and updates documentation and tests accordingly.

Changes

Cohort / File(s) Summary
Web UI Model Display Names
apps/web/components/app/feed/ActivityCard.tsx, apps/web/lib/utils/post-share.ts, apps/web/lib/utils/recap.ts
Extended prettifyModel function to recognize Gemini, Qwen, and Mistral model variants. Updated getShareModelLabel with priority ordering for Claude, then delegated lookup for other families. Expanded MODEL_DISPLAY_NAMES mapping with new model keys.
CLI Provider Integration Modules
packages/cli/src/lib/gemini.ts, packages/cli/src/lib/qwen.ts, packages/cli/src/lib/mistral.ts
Added three new provider modules implementing data retrieval, parsing, and normalization. Each exposes RunAsync and ParseOutput functions, handles date filtering, token aggregation, cost calculation, and returns structured CcusageDailyEntry arrays with anomalies and metadata.
CLI Data Aggregation & Types
packages/cli/src/commands/push.ts, packages/cli/src/lib/ccusage.ts
Updated mergeEntries function signature to accept variadic additional provider sources beyond Claude and Codex. Extended NormalizationAnomaly.source union to include "gemini", "qwen", "mistral".
CLI Test Setup
packages/cli/__tests__/commands/push.test.ts, packages/cli/__tests__/flows/cli-sync-flow.test.ts
Added mock definitions and initialization for runGeminiRawAsync, parseGeminiOutput, runQwenRawAsync, parseQwenOutput, runMistralRawAsync, parseMistralOutput. Updated test expectations to handle third provider execution path.
Documentation
docs/CHANGELOG.md, docs/DECISIONS.md
Added changelog entry describing new provider support with parallel execution and usage pipeline merging. Added decision document detailing multi-provider usage tracking architecture and fallback behavior.

Sequence Diagram

sequenceDiagram
    participant CLI as CLI Push Command
    participant Claude as Claude Provider
    participant Codex as Codex Provider
    participant Gemini as Gemini Provider
    participant Qwen as Qwen Provider
    participant Mistral as Mistral Provider
    participant Merge as mergeEntries()
    participant Output as Daily Usage Pipeline

    CLI->>Claude: runClaudeRawAsync(dates)
    Claude-->>CLI: raw usage data
    CLI->>Codex: runCodexRawAsync(dates)
    Codex-->>CLI: raw usage data
    CLI->>Gemini: runGeminiRawAsync(dates)
    Gemini-->>CLI: raw usage data
    CLI->>Qwen: runQwenRawAsync(dates)
    Qwen-->>CLI: raw usage data
    CLI->>Mistral: runMistralRawAsync(dates)
    Mistral-->>CLI: raw usage data

    CLI->>Claude: parseClaudeOutput(raw)
    Claude-->>CLI: CcusageDailyEntry[]
    CLI->>Codex: parseCodexOutput(raw)
    Codex-->>CLI: CcusageDailyEntry[]
    CLI->>Gemini: parseGeminiOutput(raw)
    Gemini-->>CLI: CcusageDailyEntry[]
    CLI->>Qwen: parseQwenOutput(raw)
    Qwen-->>CLI: CcusageDailyEntry[]
    CLI->>Mistral: parseMistralOutput(raw)
    Mistral-->>CLI: CcusageDailyEntry[]

    CLI->>Merge: mergeEntries(claude, codex, gemini, qwen, mistral)
    Merge->>Merge: Aggregate all sources by date
    Merge->>Merge: Compute totals & model breakdown
    Merge-->>CLI: merged CcusageDailyEntry[]
    
    CLI->>Output: Push to daily usage pipeline
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 Three new providers hop into the CLI,
Gemini, Qwen, and Mistral dance by,
Data flows parallel through our stream,
Merged with grace—a fuzzy dream!
Models prettified, usage tracked with care,
The warren grows richer, beyond compare!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely summarizes the primary change: adding support for three new provider integrations (Gemini, Qwen Code, and Mistral Vibe) to the CLI.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@jqueguiner

Copy link
Copy Markdown
Author

I guess it needs adjustments in the backend too.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/cli/src/commands/push.ts (1)

263-303: ⚠️ Potential issue | 🟠 Major

Unresolved Gemini, Qwen, and Mistral rows still get submitted.

blockedDates is derived only from codexParsed.entryMeta. The new providers can also return mode === "unresolved", so the command warns about bad normalization and then merges those rows anyway. Filter each provider's data against its own entryMeta before calling mergeEntries, and add a regression for at least one unresolved Gemini/Qwen/Mistral row.

Possible fix
-  const codexMetaByDate = new Map((codexParsed.entryMeta ?? []).map((row) => [row.date, row.meta]));
-  const blockedDates = new Set<string>();
-
-  for (const [date, meta] of codexMetaByDate) {
-    if (meta.mode === "unresolved") {
-      blockedDates.add(date);
-    }
-  }
-
-  if (blockedDates.size > 0) {
-    const blocked = [...blockedDates].sort();
-    const reason = "unresolved codex normalization";
-    console.log(`Warning: skipping Codex rows for ${blocked.length} date(s) due to ${reason}: ${blocked.join(", ")}`);
-  }
-
-  const codexEntries = codexParsed.data.filter((entry) => !blockedDates.has(entry.date));
+  const dropUnresolved = (
+    provider: string,
+    parsed: { data: CcusageDailyEntry[]; entryMeta?: Array<{ date: string; meta: { mode: string } }> },
+  ) => {
+    const blockedDates = new Set(
+      (parsed.entryMeta ?? [])
+        .filter((row) => row.meta.mode === "unresolved")
+        .map((row) => row.date),
+    );
+    if (blockedDates.size > 0) {
+      console.log(
+        `Warning: skipping ${provider} rows for ${blockedDates.size} date(s) due to unresolved normalization: ${[...blockedDates].sort().join(", ")}`,
+      );
+    }
+    return parsed.data.filter((entry) => !blockedDates.has(entry.date));
+  };
+
+  const codexEntries = dropUnresolved("Codex", codexParsed);
+  const geminiEntries = dropUnresolved("Gemini", geminiParsed);
+  const qwenEntries = dropUnresolved("Qwen", qwenParsed);
+  const mistralEntries = dropUnresolved("Mistral", mistralParsed);
@@
-    geminiParsed.data,
-    qwenParsed.data,
-    mistralParsed.data,
+    geminiEntries,
+    qwenEntries,
+    mistralEntries,
   );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/commands/push.ts` around lines 263 - 303, The current code
only derives blockedDates from codexParsed.entryMeta so unresolved rows from
Gemini/Qwen/Mistral still get merged; create an entryMeta->date map and blocked
date Set for each provider (e.g., geminiParsed.entryMeta, qwenParsed.entryMeta,
mistralParsed.entryMeta) similar to codexMetaByDate/blockedDates, filter each
provider's data (geminiParsed.data, qwenParsed.data, mistralParsed.data, and
codexParsed.data) to remove entries whose date is in that provider's blocked set
(replace codexEntries with per-provider filtered arrays), then pass those
filtered arrays into mergeEntries; also add a regression test that submits at
least one unresolved Gemini or Qwen or Mistral row to ensure these are skipped.
🧹 Nitpick comments (2)
packages/cli/src/lib/qwen.ts (1)

154-178: Consider validating parsed array elements.

The parsed as CcusageDailyEntry[] assertion (Line 170) trusts that the JSON structure matches without field validation. Since runQwenRawAsync serializes the data itself, this is safe in practice. However, for robustness against future refactoring, consider adding a quick shape check or documenting this assumption.

💡 Optional: Add minimal validation
   if (!Array.isArray(parsed)) {
     return { data: [], anomalies: [], normalizationSummary: summarizeNormalization([]), entryMeta: [] };
   }

-  const data = parsed as CcusageDailyEntry[];
+  // Trust the shape since we serialized it ourselves in runQwenRawAsync
+  const data = parsed as CcusageDailyEntry[];

   return {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/lib/qwen.ts` around lines 154 - 178, In parseQwenOutput, the
code unconditionally casts parsed to CcusageDailyEntry[] (parsed as
CcusageDailyEntry[]) which can hide malformed data; either add a minimal runtime
shape check (e.g., ensure parsed is an array and each item has required keys
like the CcusageDailyEntry fields) before accepting it or add a clear comment
documenting the invariant coming from runQwenRawAsync; locate parseQwenOutput
and the parsed/CcusageDailyEntry usage and implement the short validation or
documentation change accordingly.
packages/cli/src/lib/mistral.ts (1)

68-89: Helper functions are appropriate, though toIsoDate is duplicated.

The extractDate function handles both ISO timestamps and folder naming patterns correctly. Note that toIsoDate is identical to the one in qwen.ts — consider extracting to a shared utility if more providers are added in the future.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/lib/mistral.ts` around lines 68 - 89, The toIsoDate function
is duplicated across providers (e.g., toIsoDate in this module and in qwen.ts);
extract it into a shared utility module (e.g., create a util function named
toIsoDate in a common helpers file), export it, replace the local toIsoDate with
an import, and update any references (including extractDate which can continue
to coexist) so both this module and qwen.ts import the single shared toIsoDate;
ensure module exports/imports are correct and run type checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/cli/src/lib/gemini.ts`:
- Around line 33-35: The code currently treats any non-"[]" gcusage string as
valid; change the logic in runGeminiRawAsync where execGcusageAsync is called
(the block around execGcusageAsync([...]) and the duplicate at the later
occurrence) to JSON.parse the result and check Array.isArray(parsed) — only
return the raw result when parsed is an array; if parsing fails or parsed is not
an array, do not return (fall through to the session-reader fallback) and
optionally emit a low-confidence anomaly/log entry instead of letting
parseGeminiOutput silently treat non-arrays as empty.

---

Outside diff comments:
In `@packages/cli/src/commands/push.ts`:
- Around line 263-303: The current code only derives blockedDates from
codexParsed.entryMeta so unresolved rows from Gemini/Qwen/Mistral still get
merged; create an entryMeta->date map and blocked date Set for each provider
(e.g., geminiParsed.entryMeta, qwenParsed.entryMeta, mistralParsed.entryMeta)
similar to codexMetaByDate/blockedDates, filter each provider's data
(geminiParsed.data, qwenParsed.data, mistralParsed.data, and codexParsed.data)
to remove entries whose date is in that provider's blocked set (replace
codexEntries with per-provider filtered arrays), then pass those filtered arrays
into mergeEntries; also add a regression test that submits at least one
unresolved Gemini or Qwen or Mistral row to ensure these are skipped.

---

Nitpick comments:
In `@packages/cli/src/lib/mistral.ts`:
- Around line 68-89: The toIsoDate function is duplicated across providers
(e.g., toIsoDate in this module and in qwen.ts); extract it into a shared
utility module (e.g., create a util function named toIsoDate in a common helpers
file), export it, replace the local toIsoDate with an import, and update any
references (including extractDate which can continue to coexist) so both this
module and qwen.ts import the single shared toIsoDate; ensure module
exports/imports are correct and run type checks.

In `@packages/cli/src/lib/qwen.ts`:
- Around line 154-178: In parseQwenOutput, the code unconditionally casts parsed
to CcusageDailyEntry[] (parsed as CcusageDailyEntry[]) which can hide malformed
data; either add a minimal runtime shape check (e.g., ensure parsed is an array
and each item has required keys like the CcusageDailyEntry fields) before
accepting it or add a clear comment documenting the invariant coming from
runQwenRawAsync; locate parseQwenOutput and the parsed/CcusageDailyEntry usage
and implement the short validation or documentation change accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 25b6282e-ce9e-47e5-8fad-7e20ac5b08fc

📥 Commits

Reviewing files that changed from the base of the PR and between f72cadb and f0f1359.

📒 Files selected for processing (12)
  • apps/web/components/app/feed/ActivityCard.tsx
  • apps/web/lib/utils/post-share.ts
  • apps/web/lib/utils/recap.ts
  • docs/CHANGELOG.md
  • docs/DECISIONS.md
  • packages/cli/__tests__/commands/push.test.ts
  • packages/cli/__tests__/flows/cli-sync-flow.test.ts
  • packages/cli/src/commands/push.ts
  • packages/cli/src/lib/ccusage.ts
  • packages/cli/src/lib/gemini.ts
  • packages/cli/src/lib/mistral.ts
  • packages/cli/src/lib/qwen.ts

Comment on lines +33 to +35
const result = await execGcusageAsync(["--json", "--period", "day", "--since", since, "--until", until]);
// gcusage returns "[]" when no telemetry.log exists — fall through to session reader
if (result.trim() !== "[]") return result;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Validate gcusage output before skipping the session fallback.

Only literal "[]" falls through today. If gcusage returns any other valid JSON shape, runGeminiRawAsync accepts it and parseGeminiOutput then silently turns the provider into data: [], even when ~/.gemini/tmp/*/chats has usable data. Please require an array payload here, or surface a low-confidence anomaly instead of treating non-arrays as empty.

Also applies to: 243-245

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/lib/gemini.ts` around lines 33 - 35, The code currently
treats any non-"[]" gcusage string as valid; change the logic in
runGeminiRawAsync where execGcusageAsync is called (the block around
execGcusageAsync([...]) and the duplicate at the later occurrence) to JSON.parse
the result and check Array.isArray(parsed) — only return the raw result when
parsed is an array; if parsing fails or parsed is not an array, do not return
(fall through to the session-reader fallback) and optionally emit a
low-confidence anomaly/log entry instead of letting parseGeminiOutput silently
treat non-arrays as empty.

ohong added a commit that referenced this pull request Mar 11, 2026
The function computed `normalized = model.trim()` but then used raw
`model` for most regex tests and .includes() calls. Whitespace-padded
model names failed anchor-based matches (^o3, ^o4, ^gpt-) and returned
untrimmed strings for unknown models. All references now use `normalized`.

Exported the function so tests import from source instead of copy-pasting.
Added 12 unit tests covering whitespace, all provider patterns, and
legacy fallbacks.

Created 6 GitHub issues for larger items found during TD review:
- #31 Race condition in usage/submit multi-device flow
- #32 Deduplicate prettifyModel across 3 files
- #33 Add rate limiting to DELETE endpoints
- #34 Add missing test coverage for 17 untested API routes
- #35 PR #22 multi-provider support review feedback
- #36 Validate leaderboard cursor input

Nightshift-Task: td-review
Nightshift-Ref: https://github.com/marcus/nightshift

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ohong added a commit that referenced this pull request Mar 12, 2026
* fix: prettifyModel uses normalized (trimmed) input consistently

The function computed `normalized = model.trim()` but then used raw
`model` for most regex tests and .includes() calls. Whitespace-padded
model names failed anchor-based matches (^o3, ^o4, ^gpt-) and returned
untrimmed strings for unknown models. All references now use `normalized`.

Exported the function so tests import from source instead of copy-pasting.
Added 12 unit tests covering whitespace, all provider patterns, and
legacy fallbacks.

Created 6 GitHub issues for larger items found during TD review:
- #31 Race condition in usage/submit multi-device flow
- #32 Deduplicate prettifyModel across 3 files
- #33 Add rate limiting to DELETE endpoints
- #34 Add missing test coverage for 17 untested API routes
- #35 PR #22 multi-provider support review feedback
- #36 Validate leaderboard cursor input

Nightshift-Task: td-review
Nightshift-Ref: https://github.com/marcus/nightshift

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: update CHANGELOG issue ref from duplicate #32 to canonical #25

Closed duplicate issues #31-36 (duplicates of #24-29 from prior iteration).

Nightshift-Task: td-review
Nightshift-Ref: https://github.com/marcus/nightshift

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
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.

1 participant