Skip to content

fix(gamification): dedup badge unlock via user_badges so events don't re-fire every request (#3472) - #3565

Merged
diegosouzapw merged 1 commit into
release/v3.8.20from
fix/3472-badge-unlock-once
Jun 10, 2026
Merged

diegosouzapw merged 1 commit into
release/v3.8.20from
fix/3472-badge-unlock-once

Conversation

@diegosouzapw

Copy link
Copy Markdown
Owner

Closes #3472

Problem: checkAndUnlockBadge guarded with getBadges(), which INNER-JOINs badge_definitions. That table is empty in production (seedBuiltinBadges is never wired at startup), so the guard always reported "not earned" and re-emitted events.badge_unlocked for first-token/token-consumer/weekly-warrior on every request (log spam + redundant writes).

Fix: added hasBadge(apiKeyId, badgeId) to db/gamification.ts that reads user_badges directly (no JOIN), and switched the guard to it — dedup is now correct regardless of whether badge_definitions is seeded. Re-exported from localDb.ts (check-db-rules: 50 re-exported, OK).

Test (Rule #18): tests/unit/gamification/badge-unlock-once-3472.test.ts — RED before hasBadge existed; proves hasBadge sees an award even when getBadges is blind (empty definitions) and that a repeated unlock stays a single row. events/badges/db-gamification suites still green.

Note: seeding badge_definitions so badges display is tracked separately under #3484.

@diegosouzapw
diegosouzapw merged commit 624aa01 into release/v3.8.20 Jun 10, 2026
@diegosouzapw
diegosouzapw deleted the fix/3472-badge-unlock-once branch June 10, 2026 13:09

@gemini-code-assist gemini-code-assist Bot left a comment

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.

Code Review

This pull request fixes a bug where badge-unlock events re-fired on every request by introducing a hasBadge helper that queries the user_badges table directly, bypassing the empty badge_definitions table. The review feedback correctly points out that the newly added test file uses Node's built-in node:test and node:assert/strict instead of Vitest, which is the project's standard testing framework, and provides a code suggestion to migrate the test to Vitest.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +1 to +45
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { unlockBadge, getBadges, hasBadge } from "../../../src/lib/db/gamification";
import { getDbInstance } from "../../../src/lib/db/core";

// Regression for #3472: badge_definitions is empty in production (seedBuiltinBadges is never
// wired at startup). The old "already unlocked?" guard in checkAndUnlockBadge used getBadges(),
// which INNER-JOINs badge_definitions — so it returned [] even after a badge was awarded, and
// the unlock event (events.badge_unlocked) was re-emitted on EVERY request. The guard must read
// user_badges directly so dedup works regardless of whether badge_definitions is populated.

describe("#3472 badge unlock dedup is independent of badge_definitions", () => {
it("hasBadge() sees an awarded badge even when badge_definitions has no matching row", () => {
const key = `t3472-${Date.now()}`;
const badgeId = `first-token-3472-${Date.now()}`;
const db = getDbInstance();
try {
unlockBadge(key, badgeId);
// getBadges INNER-JOINs badge_definitions → blind to this award (the bug surface).
assert.equal(getBadges(key).length, 0, "getBadges is blind without a definition row");
// The fixed guard reads user_badges directly.
assert.equal(hasBadge(key, badgeId), true, "hasBadge must see the awarded badge");
} finally {
db.prepare("DELETE FROM user_badges WHERE api_key_id = ?").run(key);
}
});

it("hasBadge() is false before award and a repeated unlock stays a single row", () => {
const key = `t3472b-${Date.now()}`;
const badgeId = `token-consumer-3472-${Date.now()}`;
const db = getDbInstance();
try {
assert.equal(hasBadge(key, badgeId), false, "no badge before award");
unlockBadge(key, badgeId);
unlockBadge(key, badgeId); // INSERT OR IGNORE → still one row
assert.equal(hasBadge(key, badgeId), true);
const count = db
.prepare("SELECT COUNT(*) AS n FROM user_badges WHERE api_key_id = ? AND badge_id = ?")
.get(key, badgeId) as { n: number };
assert.equal(count.n, 1, "exactly one badge row after repeated unlock");
} finally {
db.prepare("DELETE FROM user_badges WHERE api_key_id = ?").run(key);
}
});
});

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.

medium

The project uses Vitest as its primary testing framework (as indicated by vitest.config.ts and the repository style guide). To maintain consistency across the codebase and ensure proper integration with Vitest's test runner, reporters, and coverage tools, please use vitest instead of the built-in Node.js node:test and node:assert/strict modules.

import { describe, it, expect } from "vitest";
import { unlockBadge, getBadges, hasBadge } from "../../../src/lib/db/gamification";
import { getDbInstance } from "../../../src/lib/db/core";

// Regression for #3472: badge_definitions is empty in production (seedBuiltinBadges is never
// wired at startup). The old "already unlocked?" guard in checkAndUnlockBadge used getBadges(),
// which INNER-JOINs badge_definitions — so it returned [] even after a badge was awarded, and
// the unlock event (events.badge_unlocked) was re-emitted on EVERY request. The guard must read
// user_badges directly so dedup works regardless of whether badge_definitions is populated.

describe("#3472 badge unlock dedup is independent of badge_definitions", () => {
  it("hasBadge() sees an awarded badge even when badge_definitions has no matching row", () => {
    const key = `t3472-${Date.now()}`;
    const badgeId = `first-token-3472-${Date.now()}`;
    const db = getDbInstance();
    try {
      unlockBadge(key, badgeId);
      // getBadges INNER-JOINs badge_definitions → blind to this award (the bug surface).
      expect(getBadges(key).length).toBe(0);
      // The fixed guard reads user_badges directly.
      expect(hasBadge(key, badgeId)).toBe(true);
    } finally {
      db.prepare("DELETE FROM user_badges WHERE api_key_id = ?").run(key);
    }
  });

  it("hasBadge() is false before award and a repeated unlock stays a single row", () => {
    const key = `t3472b-${Date.now()}`;
    const badgeId = `token-consumer-3472-${Date.now()}`;
    const db = getDbInstance();
    try {
      expect(hasBadge(key, badgeId)).toBe(false);
      unlockBadge(key, badgeId);
      unlockBadge(key, badgeId); // INSERT OR IGNORE → still one row
      expect(hasBadge(key, badgeId)).toBe(true);
      const count = db
        .prepare("SELECT COUNT(*) AS n FROM user_badges WHERE api_key_id = ? AND badge_id = ?")
        .get(key, badgeId) as { n: number };
      expect(count.n).toBe(1);
    } finally {
      db.prepare("DELETE FROM user_badges WHERE api_key_id = ?").run(key);
    }
  });
});

diegosouzapw added a commit that referenced this pull request Jun 10, 2026
* chore(release): open v3.8.20 development cycle

* fix(images): prefer bare combos over image aliases (#3527)

Integrated into release/v3.8.20

* fix(translator): map Codex local_shell tool (#3534)

Integrated into release/v3.8.20

* fix(usage): make opencode-go quota fetcher fail-open instead of throwing 500 (#3522)

Integrated into release/v3.8.20

* Fix Runtime page breaker state rendering (#3533)

Integrated into release/v3.8.20

* Expose provider breaker degradation threshold setting (#3535)

Integrated into release/v3.8.20

* fix(executor): strip provider prefix from versioned built-in tool model field (#3532)

Integrated into release/v3.8.20

* feat(providers): add Claude Fable 5 support (#3524)

Integrated into release/v3.8.20

* feat(resilience): add global provider cooldown tracking to prevent combo re-walking (#3556)

Integrated into release/v3.8.20 (default OFF, opt-in)

* fix(translator): scope thoughtSignature bypass to Antigravity/CLI only (#3560)

Integrated into release/v3.8.20. Co-authored-by: Six7Day <six7day@gmail.com>

* fix(routing): normalize thinking:disabled for combo-substituted models that reject it (#3554) (#3563)

Integrated into release/v3.8.20

* fix(usage): accept 0/empty budget limits so the dashboard can save and clear (#3537) (#3564)

Integrated into release/v3.8.20

* docs(changelog): credit @Six7Day for #3560 thoughtSignature fix (#3414)

The #3560 squash co-author trailer landed inline (unparsed by GitHub), so add
an explicit CHANGELOG credit ensuring @Six7Day (original #3414) and @oyi77 are
on the public record for the Gemini thoughtSignature fix.

* fix(gamification): dedup badge unlock via user_badges so events don't re-fire every request (#3472) (#3565)

Integrated into release/v3.8.20

* fix(routing): pass through 'auto' keyword on codex /v1/responses instead of rewriting to codex/auto (#3509) (#3566)

Integrated into release/v3.8.20

* fix(cli-tools): normalize apiKey null in guide-settings schema so cloud-mode config saves (#3552) (#3567)

Integrated into release/v3.8.20

* fix(catalog): reclassify PublicAI from keyless to one-time-initial (requires API key) (#3558) (#3568)

Integrated into release/v3.8.20

* fix(gemini-web): surface missing Playwright browser as actionable 503 + cooldown hint, not a retryable 500 loop (#3516) (#3570)

Integrated into release/v3.8.20

* fix(security): sanitize raw err.message in web executors + embeddings/search response bodies (Rule #12) (#3494, #3495) (#3573)

Integrated into release/v3.8.20

* fix(dashboard): point CustomHostsManager + FeatureFlagsGrid at real routes (#3486, #3487) (#3574)

Integrated into release/v3.8.20

* chore(providers): remove dead krutrim entry (#3483) + docs(api): fix agent-bridge per-agent state route (#3489) (#3575)

Integrated into release/v3.8.20

* docs(api): correct API_REFERENCE.md paths for skills/plugins/admin/cache/acp/system-info (#3497) (#3577)

Integrated into release/v3.8.20

* fix(proxy): drive SOCKS5 UI option from runtime ENABLE_SOCKS5_PROXY, not build-time NEXT_PUBLIC (#3508) (#3579)

Integrated into release/v3.8.20

* fix(playground): filter playground models by node prefix so custom-endpoint models appear (#3505) (#3581)

Integrated into release/v3.8.20

* fix(usage): show an informative message instead of a blank Kiro quota card when no usage breakdown (#3506) (#3582)

Integrated into release/v3.8.20

* docs(changelog): add the #3506 Kiro quota entry (missed in #3582 due to a stale-base CHANGELOG anchor) (#3583)

Integrated into release/v3.8.20

* fix(auto-update): use stable PROJECT_ROOT walker, not frozen process.cwd() (#3561)

Integrated into release/v3.8.20. Auto-update PROJECT_ROOT now uses a stable __dirname-anchored upward walker instead of the no-op process.cwd() resolver.

* fix: address PR #3518 review comments (lifecycle hooks, regex, indentation, route params) (#3562)

Integrated into release/v3.8.20. Addresses #3518 review: regex literals, logs/[id] route params (Next 16), indentation, and wires plugin lifecycle hooks (onInstall/onActivate/onDeactivate/onUninstall) in the loader so manager.ts can register them. Adds Rule #18 regression test.

* docs(changelog): credit @ViFigueiredo (#3423) for PROJECT_ROOT + log #3561/#3562 (v3.8.20)

* fix: openai to gemini incorrectly translates historical tool calls into text (#3569)

Integrated into release/v3.8.20. Standard Gemini direct path now maps historical tool calls to native functionCall/functionResponse parts (signaturelessToolCallMode: native) instead of inert text — validated against the real Gemini API (gemini-2.5-flash returns 200 for signatureless native functionCall, even with tools+thinking; Hard Rule #18). Eliminates the text-serialization leak. Antigravity/CLI sentinel path (#3560) untouched.

* docs(changelog)+test: reconcile standard-Gemini native mode (#3569) — update round-2 rationale comment + log VPS validation

* docs(changelog): reconcile v3.8.20 — add 9 missing bullets + move [Unreleased] to versioned section

* docs(changelog): complete v3.8.20 reconciliation — 27 bullets, 11 contributors

---------

Co-authored-by: Alexander Averyanov <alex@averyan.ru>
Co-authored-by: Hakan Kurşun <bykamaka@gmail.com>
Co-authored-by: Wilson <pedbookmed@gmail.com>
Co-authored-by: Randi <55005611+rdself@users.noreply.github.com>
Co-authored-by: Giorgos Giakoumettis <giorgos@yiakoumettis.gr>
Co-authored-by: PizzaV <103120356+pizzav-xyz@users.noreply.github.com>
Co-authored-by: Paijo <14921983+oyi77@users.noreply.github.com>
Co-authored-by: Markus Hartung <mail@hartmark.se>
rafacpti23 pushed a commit to rafacpti23/OmniRoute that referenced this pull request Jun 10, 2026
* chore(release): open v3.8.20 development cycle

* fix(images): prefer bare combos over image aliases (diegosouzapw#3527)

Integrated into release/v3.8.20

* fix(translator): map Codex local_shell tool (diegosouzapw#3534)

Integrated into release/v3.8.20

* fix(usage): make opencode-go quota fetcher fail-open instead of throwing 500 (diegosouzapw#3522)

Integrated into release/v3.8.20

* Fix Runtime page breaker state rendering (diegosouzapw#3533)

Integrated into release/v3.8.20

* Expose provider breaker degradation threshold setting (diegosouzapw#3535)

Integrated into release/v3.8.20

* fix(executor): strip provider prefix from versioned built-in tool model field (diegosouzapw#3532)

Integrated into release/v3.8.20

* feat(providers): add Claude Fable 5 support (diegosouzapw#3524)

Integrated into release/v3.8.20

* feat(resilience): add global provider cooldown tracking to prevent combo re-walking (diegosouzapw#3556)

Integrated into release/v3.8.20 (default OFF, opt-in)

* fix(translator): scope thoughtSignature bypass to Antigravity/CLI only (diegosouzapw#3560)

Integrated into release/v3.8.20. Co-authored-by: Six7Day <six7day@gmail.com>

* fix(routing): normalize thinking:disabled for combo-substituted models that reject it (diegosouzapw#3554) (diegosouzapw#3563)

Integrated into release/v3.8.20

* fix(usage): accept 0/empty budget limits so the dashboard can save and clear (diegosouzapw#3537) (diegosouzapw#3564)

Integrated into release/v3.8.20

* docs(changelog): credit @Six7Day for diegosouzapw#3560 thoughtSignature fix (diegosouzapw#3414)

The diegosouzapw#3560 squash co-author trailer landed inline (unparsed by GitHub), so add
an explicit CHANGELOG credit ensuring @Six7Day (original diegosouzapw#3414) and @oyi77 are
on the public record for the Gemini thoughtSignature fix.

* fix(gamification): dedup badge unlock via user_badges so events don't re-fire every request (diegosouzapw#3472) (diegosouzapw#3565)

Integrated into release/v3.8.20

* fix(routing): pass through 'auto' keyword on codex /v1/responses instead of rewriting to codex/auto (diegosouzapw#3509) (diegosouzapw#3566)

Integrated into release/v3.8.20

* fix(cli-tools): normalize apiKey null in guide-settings schema so cloud-mode config saves (diegosouzapw#3552) (diegosouzapw#3567)

Integrated into release/v3.8.20

* fix(catalog): reclassify PublicAI from keyless to one-time-initial (requires API key) (diegosouzapw#3558) (diegosouzapw#3568)

Integrated into release/v3.8.20

* fix(gemini-web): surface missing Playwright browser as actionable 503 + cooldown hint, not a retryable 500 loop (diegosouzapw#3516) (diegosouzapw#3570)

Integrated into release/v3.8.20

* fix(security): sanitize raw err.message in web executors + embeddings/search response bodies (Rule diegosouzapw#12) (diegosouzapw#3494, diegosouzapw#3495) (diegosouzapw#3573)

Integrated into release/v3.8.20

* fix(dashboard): point CustomHostsManager + FeatureFlagsGrid at real routes (diegosouzapw#3486, diegosouzapw#3487) (diegosouzapw#3574)

Integrated into release/v3.8.20

* chore(providers): remove dead krutrim entry (diegosouzapw#3483) + docs(api): fix agent-bridge per-agent state route (diegosouzapw#3489) (diegosouzapw#3575)

Integrated into release/v3.8.20

* docs(api): correct API_REFERENCE.md paths for skills/plugins/admin/cache/acp/system-info (diegosouzapw#3497) (diegosouzapw#3577)

Integrated into release/v3.8.20

* fix(proxy): drive SOCKS5 UI option from runtime ENABLE_SOCKS5_PROXY, not build-time NEXT_PUBLIC (diegosouzapw#3508) (diegosouzapw#3579)

Integrated into release/v3.8.20

* fix(playground): filter playground models by node prefix so custom-endpoint models appear (diegosouzapw#3505) (diegosouzapw#3581)

Integrated into release/v3.8.20

* fix(usage): show an informative message instead of a blank Kiro quota card when no usage breakdown (diegosouzapw#3506) (diegosouzapw#3582)

Integrated into release/v3.8.20

* docs(changelog): add the diegosouzapw#3506 Kiro quota entry (missed in diegosouzapw#3582 due to a stale-base CHANGELOG anchor) (diegosouzapw#3583)

Integrated into release/v3.8.20

* fix(auto-update): use stable PROJECT_ROOT walker, not frozen process.cwd() (diegosouzapw#3561)

Integrated into release/v3.8.20. Auto-update PROJECT_ROOT now uses a stable __dirname-anchored upward walker instead of the no-op process.cwd() resolver.

* fix: address PR diegosouzapw#3518 review comments (lifecycle hooks, regex, indentation, route params) (diegosouzapw#3562)

Integrated into release/v3.8.20. Addresses diegosouzapw#3518 review: regex literals, logs/[id] route params (Next 16), indentation, and wires plugin lifecycle hooks (onInstall/onActivate/onDeactivate/onUninstall) in the loader so manager.ts can register them. Adds Rule diegosouzapw#18 regression test.

* docs(changelog): credit @ViFigueiredo (diegosouzapw#3423) for PROJECT_ROOT + log diegosouzapw#3561/diegosouzapw#3562 (v3.8.20)

* fix: openai to gemini incorrectly translates historical tool calls into text (diegosouzapw#3569)

Integrated into release/v3.8.20. Standard Gemini direct path now maps historical tool calls to native functionCall/functionResponse parts (signaturelessToolCallMode: native) instead of inert text — validated against the real Gemini API (gemini-2.5-flash returns 200 for signatureless native functionCall, even with tools+thinking; Hard Rule diegosouzapw#18). Eliminates the text-serialization leak. Antigravity/CLI sentinel path (diegosouzapw#3560) untouched.

* docs(changelog)+test: reconcile standard-Gemini native mode (diegosouzapw#3569) — update round-2 rationale comment + log VPS validation

* docs(changelog): reconcile v3.8.20 — add 9 missing bullets + move [Unreleased] to versioned section

* docs(changelog): complete v3.8.20 reconciliation — 27 bullets, 11 contributors

---------

Co-authored-by: Alexander Averyanov <alex@averyan.ru>
Co-authored-by: Hakan Kurşun <bykamaka@gmail.com>
Co-authored-by: Wilson <pedbookmed@gmail.com>
Co-authored-by: Randi <55005611+rdself@users.noreply.github.com>
Co-authored-by: Giorgos Giakoumettis <giorgos@yiakoumettis.gr>
Co-authored-by: PizzaV <103120356+pizzav-xyz@users.noreply.github.com>
Co-authored-by: Paijo <14921983+oyi77@users.noreply.github.com>
Co-authored-by: Markus Hartung <mail@hartmark.se>
HouMinXi pushed a commit to HouMinXi/OmniRoute that referenced this pull request Aug 2, 2026
* chore(release): open v3.8.20 development cycle

* fix(images): prefer bare combos over image aliases (diegosouzapw#3527)

Integrated into release/v3.8.20

* fix(translator): map Codex local_shell tool (diegosouzapw#3534)

Integrated into release/v3.8.20

* fix(usage): make opencode-go quota fetcher fail-open instead of throwing 500 (diegosouzapw#3522)

Integrated into release/v3.8.20

* Fix Runtime page breaker state rendering (diegosouzapw#3533)

Integrated into release/v3.8.20

* Expose provider breaker degradation threshold setting (diegosouzapw#3535)

Integrated into release/v3.8.20

* fix(executor): strip provider prefix from versioned built-in tool model field (diegosouzapw#3532)

Integrated into release/v3.8.20

* feat(providers): add Claude Fable 5 support (diegosouzapw#3524)

Integrated into release/v3.8.20

* feat(resilience): add global provider cooldown tracking to prevent combo re-walking (diegosouzapw#3556)

Integrated into release/v3.8.20 (default OFF, opt-in)

* fix(translator): scope thoughtSignature bypass to Antigravity/CLI only (diegosouzapw#3560)

Integrated into release/v3.8.20. Co-authored-by: Six7Day <six7day@gmail.com>

* fix(routing): normalize thinking:disabled for combo-substituted models that reject it (diegosouzapw#3554) (diegosouzapw#3563)

Integrated into release/v3.8.20

* fix(usage): accept 0/empty budget limits so the dashboard can save and clear (diegosouzapw#3537) (diegosouzapw#3564)

Integrated into release/v3.8.20

* docs(changelog): credit @Six7Day for diegosouzapw#3560 thoughtSignature fix (diegosouzapw#3414)

The diegosouzapw#3560 squash co-author trailer landed inline (unparsed by GitHub), so add
an explicit CHANGELOG credit ensuring @Six7Day (original diegosouzapw#3414) and @oyi77 are
on the public record for the Gemini thoughtSignature fix.

* fix(gamification): dedup badge unlock via user_badges so events don't re-fire every request (diegosouzapw#3472) (diegosouzapw#3565)

Integrated into release/v3.8.20

* fix(routing): pass through 'auto' keyword on codex /v1/responses instead of rewriting to codex/auto (diegosouzapw#3509) (diegosouzapw#3566)

Integrated into release/v3.8.20

* fix(cli-tools): normalize apiKey null in guide-settings schema so cloud-mode config saves (diegosouzapw#3552) (diegosouzapw#3567)

Integrated into release/v3.8.20

* fix(catalog): reclassify PublicAI from keyless to one-time-initial (requires API key) (diegosouzapw#3558) (diegosouzapw#3568)

Integrated into release/v3.8.20

* fix(gemini-web): surface missing Playwright browser as actionable 503 + cooldown hint, not a retryable 500 loop (diegosouzapw#3516) (diegosouzapw#3570)

Integrated into release/v3.8.20

* fix(security): sanitize raw err.message in web executors + embeddings/search response bodies (Rule diegosouzapw#12) (diegosouzapw#3494, diegosouzapw#3495) (diegosouzapw#3573)

Integrated into release/v3.8.20

* fix(dashboard): point CustomHostsManager + FeatureFlagsGrid at real routes (diegosouzapw#3486, diegosouzapw#3487) (diegosouzapw#3574)

Integrated into release/v3.8.20

* chore(providers): remove dead krutrim entry (diegosouzapw#3483) + docs(api): fix agent-bridge per-agent state route (diegosouzapw#3489) (diegosouzapw#3575)

Integrated into release/v3.8.20

* docs(api): correct API_REFERENCE.md paths for skills/plugins/admin/cache/acp/system-info (diegosouzapw#3497) (diegosouzapw#3577)

Integrated into release/v3.8.20

* fix(proxy): drive SOCKS5 UI option from runtime ENABLE_SOCKS5_PROXY, not build-time NEXT_PUBLIC (diegosouzapw#3508) (diegosouzapw#3579)

Integrated into release/v3.8.20

* fix(playground): filter playground models by node prefix so custom-endpoint models appear (diegosouzapw#3505) (diegosouzapw#3581)

Integrated into release/v3.8.20

* fix(usage): show an informative message instead of a blank Kiro quota card when no usage breakdown (diegosouzapw#3506) (diegosouzapw#3582)

Integrated into release/v3.8.20

* docs(changelog): add the diegosouzapw#3506 Kiro quota entry (missed in diegosouzapw#3582 due to a stale-base CHANGELOG anchor) (diegosouzapw#3583)

Integrated into release/v3.8.20

* fix(auto-update): use stable PROJECT_ROOT walker, not frozen process.cwd() (diegosouzapw#3561)

Integrated into release/v3.8.20. Auto-update PROJECT_ROOT now uses a stable __dirname-anchored upward walker instead of the no-op process.cwd() resolver.

* fix: address PR diegosouzapw#3518 review comments (lifecycle hooks, regex, indentation, route params) (diegosouzapw#3562)

Integrated into release/v3.8.20. Addresses diegosouzapw#3518 review: regex literals, logs/[id] route params (Next 16), indentation, and wires plugin lifecycle hooks (onInstall/onActivate/onDeactivate/onUninstall) in the loader so manager.ts can register them. Adds Rule diegosouzapw#18 regression test.

* docs(changelog): credit @ViFigueiredo (diegosouzapw#3423) for PROJECT_ROOT + log diegosouzapw#3561/diegosouzapw#3562 (v3.8.20)

* fix: openai to gemini incorrectly translates historical tool calls into text (diegosouzapw#3569)

Integrated into release/v3.8.20. Standard Gemini direct path now maps historical tool calls to native functionCall/functionResponse parts (signaturelessToolCallMode: native) instead of inert text — validated against the real Gemini API (gemini-2.5-flash returns 200 for signatureless native functionCall, even with tools+thinking; Hard Rule diegosouzapw#18). Eliminates the text-serialization leak. Antigravity/CLI sentinel path (diegosouzapw#3560) untouched.

* docs(changelog)+test: reconcile standard-Gemini native mode (diegosouzapw#3569) — update round-2 rationale comment + log VPS validation

* docs(changelog): reconcile v3.8.20 — add 9 missing bullets + move [Unreleased] to versioned section

* docs(changelog): complete v3.8.20 reconciliation — 27 bullets, 11 contributors

---------

Co-authored-by: Alexander Averyanov <alex@averyan.ru>
Co-authored-by: Hakan Kurşun <bykamaka@gmail.com>
Co-authored-by: Wilson <pedbookmed@gmail.com>
Co-authored-by: Randi <55005611+rdself@users.noreply.github.com>
Co-authored-by: Giorgos Giakoumettis <giorgos@yiakoumettis.gr>
Co-authored-by: PizzaV <103120356+pizzav-xyz@users.noreply.github.com>
Co-authored-by: Paijo <14921983+oyi77@users.noreply.github.com>
Co-authored-by: Markus Hartung <mail@hartmark.se>
Poid-ZA pushed a commit to Poid-ZA/OmniRoute that referenced this pull request Aug 5, 2026
* chore(release): open v3.8.20 development cycle

* fix(images): prefer bare combos over image aliases (diegosouzapw#3527)

Integrated into release/v3.8.20

* fix(translator): map Codex local_shell tool (diegosouzapw#3534)

Integrated into release/v3.8.20

* fix(usage): make opencode-go quota fetcher fail-open instead of throwing 500 (diegosouzapw#3522)

Integrated into release/v3.8.20

* Fix Runtime page breaker state rendering (diegosouzapw#3533)

Integrated into release/v3.8.20

* Expose provider breaker degradation threshold setting (diegosouzapw#3535)

Integrated into release/v3.8.20

* fix(executor): strip provider prefix from versioned built-in tool model field (diegosouzapw#3532)

Integrated into release/v3.8.20

* feat(providers): add Claude Fable 5 support (diegosouzapw#3524)

Integrated into release/v3.8.20

* feat(resilience): add global provider cooldown tracking to prevent combo re-walking (diegosouzapw#3556)

Integrated into release/v3.8.20 (default OFF, opt-in)

* fix(translator): scope thoughtSignature bypass to Antigravity/CLI only (diegosouzapw#3560)

Integrated into release/v3.8.20. Co-authored-by: Six7Day <six7day@gmail.com>

* fix(routing): normalize thinking:disabled for combo-substituted models that reject it (diegosouzapw#3554) (diegosouzapw#3563)

Integrated into release/v3.8.20

* fix(usage): accept 0/empty budget limits so the dashboard can save and clear (diegosouzapw#3537) (diegosouzapw#3564)

Integrated into release/v3.8.20

* docs(changelog): credit @Six7Day for diegosouzapw#3560 thoughtSignature fix (diegosouzapw#3414)

The diegosouzapw#3560 squash co-author trailer landed inline (unparsed by GitHub), so add
an explicit CHANGELOG credit ensuring @Six7Day (original diegosouzapw#3414) and @oyi77 are
on the public record for the Gemini thoughtSignature fix.

* fix(gamification): dedup badge unlock via user_badges so events don't re-fire every request (diegosouzapw#3472) (diegosouzapw#3565)

Integrated into release/v3.8.20

* fix(routing): pass through 'auto' keyword on codex /v1/responses instead of rewriting to codex/auto (diegosouzapw#3509) (diegosouzapw#3566)

Integrated into release/v3.8.20

* fix(cli-tools): normalize apiKey null in guide-settings schema so cloud-mode config saves (diegosouzapw#3552) (diegosouzapw#3567)

Integrated into release/v3.8.20

* fix(catalog): reclassify PublicAI from keyless to one-time-initial (requires API key) (diegosouzapw#3558) (diegosouzapw#3568)

Integrated into release/v3.8.20

* fix(gemini-web): surface missing Playwright browser as actionable 503 + cooldown hint, not a retryable 500 loop (diegosouzapw#3516) (diegosouzapw#3570)

Integrated into release/v3.8.20

* fix(security): sanitize raw err.message in web executors + embeddings/search response bodies (Rule diegosouzapw#12) (diegosouzapw#3494, diegosouzapw#3495) (diegosouzapw#3573)

Integrated into release/v3.8.20

* fix(dashboard): point CustomHostsManager + FeatureFlagsGrid at real routes (diegosouzapw#3486, diegosouzapw#3487) (diegosouzapw#3574)

Integrated into release/v3.8.20

* chore(providers): remove dead krutrim entry (diegosouzapw#3483) + docs(api): fix agent-bridge per-agent state route (diegosouzapw#3489) (diegosouzapw#3575)

Integrated into release/v3.8.20

* docs(api): correct API_REFERENCE.md paths for skills/plugins/admin/cache/acp/system-info (diegosouzapw#3497) (diegosouzapw#3577)

Integrated into release/v3.8.20

* fix(proxy): drive SOCKS5 UI option from runtime ENABLE_SOCKS5_PROXY, not build-time NEXT_PUBLIC (diegosouzapw#3508) (diegosouzapw#3579)

Integrated into release/v3.8.20

* fix(playground): filter playground models by node prefix so custom-endpoint models appear (diegosouzapw#3505) (diegosouzapw#3581)

Integrated into release/v3.8.20

* fix(usage): show an informative message instead of a blank Kiro quota card when no usage breakdown (diegosouzapw#3506) (diegosouzapw#3582)

Integrated into release/v3.8.20

* docs(changelog): add the diegosouzapw#3506 Kiro quota entry (missed in diegosouzapw#3582 due to a stale-base CHANGELOG anchor) (diegosouzapw#3583)

Integrated into release/v3.8.20

* fix(auto-update): use stable PROJECT_ROOT walker, not frozen process.cwd() (diegosouzapw#3561)

Integrated into release/v3.8.20. Auto-update PROJECT_ROOT now uses a stable __dirname-anchored upward walker instead of the no-op process.cwd() resolver.

* fix: address PR diegosouzapw#3518 review comments (lifecycle hooks, regex, indentation, route params) (diegosouzapw#3562)

Integrated into release/v3.8.20. Addresses diegosouzapw#3518 review: regex literals, logs/[id] route params (Next 16), indentation, and wires plugin lifecycle hooks (onInstall/onActivate/onDeactivate/onUninstall) in the loader so manager.ts can register them. Adds Rule diegosouzapw#18 regression test.

* docs(changelog): credit @ViFigueiredo (diegosouzapw#3423) for PROJECT_ROOT + log diegosouzapw#3561/diegosouzapw#3562 (v3.8.20)

* fix: openai to gemini incorrectly translates historical tool calls into text (diegosouzapw#3569)

Integrated into release/v3.8.20. Standard Gemini direct path now maps historical tool calls to native functionCall/functionResponse parts (signaturelessToolCallMode: native) instead of inert text — validated against the real Gemini API (gemini-2.5-flash returns 200 for signatureless native functionCall, even with tools+thinking; Hard Rule diegosouzapw#18). Eliminates the text-serialization leak. Antigravity/CLI sentinel path (diegosouzapw#3560) untouched.

* docs(changelog)+test: reconcile standard-Gemini native mode (diegosouzapw#3569) — update round-2 rationale comment + log VPS validation

* docs(changelog): reconcile v3.8.20 — add 9 missing bullets + move [Unreleased] to versioned section

* docs(changelog): complete v3.8.20 reconciliation — 27 bullets, 11 contributors

---------

Co-authored-by: Alexander Averyanov <alex@averyan.ru>
Co-authored-by: Hakan Kurşun <bykamaka@gmail.com>
Co-authored-by: Wilson <pedbookmed@gmail.com>
Co-authored-by: Randi <55005611+rdself@users.noreply.github.com>
Co-authored-by: Giorgos Giakoumettis <giorgos@yiakoumettis.gr>
Co-authored-by: PizzaV <103120356+pizzav-xyz@users.noreply.github.com>
Co-authored-by: Paijo <14921983+oyi77@users.noreply.github.com>
Co-authored-by: Markus Hartung <mail@hartmark.se>
tkgo11 pushed a commit to tkgo11/OmniRoute that referenced this pull request Sep 23, 2026
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
* chore(release): open v3.8.20 development cycle

* fix(images): prefer bare combos over image aliases (diegosouzapw#3527)

Integrated into release/v3.8.20

* fix(translator): map Codex local_shell tool (diegosouzapw#3534)

Integrated into release/v3.8.20

* fix(usage): make opencode-go quota fetcher fail-open instead of throwing 500 (diegosouzapw#3522)

Integrated into release/v3.8.20

* Fix Runtime page breaker state rendering (diegosouzapw#3533)

Integrated into release/v3.8.20

* Expose provider breaker degradation threshold setting (diegosouzapw#3535)

Integrated into release/v3.8.20

* fix(executor): strip provider prefix from versioned built-in tool model field (diegosouzapw#3532)

Integrated into release/v3.8.20

* feat(providers): add Claude Fable 5 support (diegosouzapw#3524)

Integrated into release/v3.8.20

* feat(resilience): add global provider cooldown tracking to prevent combo re-walking (diegosouzapw#3556)

Integrated into release/v3.8.20 (default OFF, opt-in)

* fix(translator): scope thoughtSignature bypass to Antigravity/CLI only (diegosouzapw#3560)

Integrated into release/v3.8.20. Co-authored-by: Six7Day <six7day@gmail.com>

* fix(routing): normalize thinking:disabled for combo-substituted models that reject it (diegosouzapw#3554) (diegosouzapw#3563)

Integrated into release/v3.8.20

* fix(usage): accept 0/empty budget limits so the dashboard can save and clear (diegosouzapw#3537) (diegosouzapw#3564)

Integrated into release/v3.8.20

* docs(changelog): credit @Six7Day for diegosouzapw#3560 thoughtSignature fix (diegosouzapw#3414)

The diegosouzapw#3560 squash co-author trailer landed inline (unparsed by GitHub), so add
an explicit CHANGELOG credit ensuring @Six7Day (original diegosouzapw#3414) and @oyi77 are
on the public record for the Gemini thoughtSignature fix.

* fix(gamification): dedup badge unlock via user_badges so events don't re-fire every request (diegosouzapw#3472) (diegosouzapw#3565)

Integrated into release/v3.8.20

* fix(routing): pass through 'auto' keyword on codex /v1/responses instead of rewriting to codex/auto (diegosouzapw#3509) (diegosouzapw#3566)

Integrated into release/v3.8.20

* fix(cli-tools): normalize apiKey null in guide-settings schema so cloud-mode config saves (diegosouzapw#3552) (diegosouzapw#3567)

Integrated into release/v3.8.20

* fix(catalog): reclassify PublicAI from keyless to one-time-initial (requires API key) (diegosouzapw#3558) (diegosouzapw#3568)

Integrated into release/v3.8.20

* fix(gemini-web): surface missing Playwright browser as actionable 503 + cooldown hint, not a retryable 500 loop (diegosouzapw#3516) (diegosouzapw#3570)

Integrated into release/v3.8.20

* fix(security): sanitize raw err.message in web executors + embeddings/search response bodies (Rule diegosouzapw#12) (diegosouzapw#3494, diegosouzapw#3495) (diegosouzapw#3573)

Integrated into release/v3.8.20

* fix(dashboard): point CustomHostsManager + FeatureFlagsGrid at real routes (diegosouzapw#3486, diegosouzapw#3487) (diegosouzapw#3574)

Integrated into release/v3.8.20

* chore(providers): remove dead krutrim entry (diegosouzapw#3483) + docs(api): fix agent-bridge per-agent state route (diegosouzapw#3489) (diegosouzapw#3575)

Integrated into release/v3.8.20

* docs(api): correct API_REFERENCE.md paths for skills/plugins/admin/cache/acp/system-info (diegosouzapw#3497) (diegosouzapw#3577)

Integrated into release/v3.8.20

* fix(proxy): drive SOCKS5 UI option from runtime ENABLE_SOCKS5_PROXY, not build-time NEXT_PUBLIC (diegosouzapw#3508) (diegosouzapw#3579)

Integrated into release/v3.8.20

* fix(playground): filter playground models by node prefix so custom-endpoint models appear (diegosouzapw#3505) (diegosouzapw#3581)

Integrated into release/v3.8.20

* fix(usage): show an informative message instead of a blank Kiro quota card when no usage breakdown (diegosouzapw#3506) (diegosouzapw#3582)

Integrated into release/v3.8.20

* docs(changelog): add the diegosouzapw#3506 Kiro quota entry (missed in diegosouzapw#3582 due to a stale-base CHANGELOG anchor) (diegosouzapw#3583)

Integrated into release/v3.8.20

* fix(auto-update): use stable PROJECT_ROOT walker, not frozen process.cwd() (diegosouzapw#3561)

Integrated into release/v3.8.20. Auto-update PROJECT_ROOT now uses a stable __dirname-anchored upward walker instead of the no-op process.cwd() resolver.

* fix: address PR diegosouzapw#3518 review comments (lifecycle hooks, regex, indentation, route params) (diegosouzapw#3562)

Integrated into release/v3.8.20. Addresses diegosouzapw#3518 review: regex literals, logs/[id] route params (Next 16), indentation, and wires plugin lifecycle hooks (onInstall/onActivate/onDeactivate/onUninstall) in the loader so manager.ts can register them. Adds Rule diegosouzapw#18 regression test.

* docs(changelog): credit @ViFigueiredo (diegosouzapw#3423) for PROJECT_ROOT + log diegosouzapw#3561/diegosouzapw#3562 (v3.8.20)

* fix: openai to gemini incorrectly translates historical tool calls into text (diegosouzapw#3569)

Integrated into release/v3.8.20. Standard Gemini direct path now maps historical tool calls to native functionCall/functionResponse parts (signaturelessToolCallMode: native) instead of inert text — validated against the real Gemini API (gemini-2.5-flash returns 200 for signatureless native functionCall, even with tools+thinking; Hard Rule diegosouzapw#18). Eliminates the text-serialization leak. Antigravity/CLI sentinel path (diegosouzapw#3560) untouched.

* docs(changelog)+test: reconcile standard-Gemini native mode (diegosouzapw#3569) — update round-2 rationale comment + log VPS validation

* docs(changelog): reconcile v3.8.20 — add 9 missing bullets + move [Unreleased] to versioned section

* docs(changelog): complete v3.8.20 reconciliation — 27 bullets, 11 contributors

---------

Co-authored-by: Alexander Averyanov <alex@averyan.ru>
Co-authored-by: Hakan Kurşun <bykamaka@gmail.com>
Co-authored-by: Wilson <pedbookmed@gmail.com>
Co-authored-by: Randi <55005611+rdself@users.noreply.github.com>
Co-authored-by: Giorgos Giakoumettis <giorgos@yiakoumettis.gr>
Co-authored-by: PizzaV <103120356+pizzav-xyz@users.noreply.github.com>
Co-authored-by: Paijo <14921983+oyi77@users.noreply.github.com>
Co-authored-by: Markus Hartung <mail@hartmark.se>
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