Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions scripts/test-layout/layout.json
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@
"catalog-opencode-go-context-window.test.ts": "codex-integration",
"catalog-remote-pull.test.ts": "codex-integration",
"catalog-retain-models.test.ts": "codex-integration",
"catalog-routed-comp-hash.test.ts": "codex-integration",
"catalog-seed-window-fill.test.ts": "codex-integration",
"catalog-slug-uniqueness-boundary.test.ts": "codex-integration",
"catalog-verbosity-default.test.ts": "codex-integration",
Expand Down
4 changes: 4 additions & 0 deletions src/codex/catalog/build-entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,10 @@ export function mergeCatalogEntriesFromObservedState({
}
const slug = String(entry.slug);
if (!isOcxAuthoredRoutedEntry(entry) || isNativeAliasCatalogEntry(entry)) continue;
// The builder no longer copies a template's comp_hash onto routed rows (#5796), but a row
// kept from disk may still carry one. Custom rows, Codex-forward aliases included, never
// reach this loop: they are rebuilt from config.
entry.comp_hash = "opencodex";
const featuredRank = featuredRankOf(slug);
entry.priority = featuredRank !== undefined
? featuredRank * priorityStride
Expand Down
5 changes: 5 additions & 0 deletions src/codex/catalog/derive-entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,11 @@ export function deriveEntry(
delete e.context_window;
delete e.max_context_window;
delete e.auto_compact_token_limit;
// Nor its comp_hash (#5796). Codex compacts a thread whenever the recorded value
// changes, and the template is whichever native row a rebuild found first, so an
// inherited value moves with rebuild order. Left unset, normalization gives every
// routed row the same "opencodex" marker.
delete e.comp_hash;
}
if (typeof e.base_instructions === "string") {
// Proxy-neutral: keep the GPT-5/OpenAI disclaimer but never advertise the opencodex proxy
Expand Down
6 changes: 5 additions & 1 deletion structure/catalog.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,11 @@ provider-wide fallback. Exact model output limits precede the provider default o
explicitly configured canonical `openai/gpt-daybreak-blue-latest` Codex-forward row from the
pinned Sol capability metadata while preserving its selector and Daybreak wire identity;
this never expands the bare/API-key model lists or rewrites the wire model to `gpt-5.6-sol`;
- clones a native template for routed `provider/model` entries;
- clones a native template for routed `provider/model` entries without its `comp_hash`, and resets
that value on opencodex rows kept from disk while their provider's discovery is degraded, so these
rows carry the fixed `"opencodex"` marker instead of whichever native row a rebuild found first;
Codex compacts a thread whenever that value changes (#5796). Codex-forward aliases keep their
native value and rows written by other tools keep theirs;
- forces strict Codex catalog fields required by the current parser;
- hides `disabledModels` without blocking direct routing (routed provider ids are excluded;
account-qualified native ids hide only that selector row; BARE native slugs hide the bare row
Expand Down
69 changes: 69 additions & 0 deletions tests/codex-integration/catalog-routed-comp-hash.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { describe, expect, test } from "bun:test";
import { buildCatalogEntries } from "../../src/codex/catalog";
import {
buildCatalogEntriesFromObservedState,
CANONICAL_NATIVE_CATALOG_CONTENT_POLICY,
mergeCatalogEntriesFromObservedState,
} from "../../src/codex/catalog/sync";
import type { RawEntry } from "../../src/codex/catalog/parsing";

/**
* A routed row is cloned from whichever native row a rebuild picks as the template, and it
* used to keep that row's `comp_hash` (#5796). Codex compacts a thread when consecutive turns
* record different values, so a rebuild that picked another template compacted every active
* routed thread.
*/
describe("catalog routed comp_hash (#5796)", () => {
const routedHash = (compHash: string | undefined) => buildCatalogEntries(
{
slug: "gpt-5.5",
display_name: "gpt-5.5",
description: "Native GPT model",
priority: 1,
visibility: "list",
base_instructions: "You are Codex, a coding agent based on GPT-5.",
...(compHash === undefined ? {} : { comp_hash: compHash }),
},
[],
[{ provider: "local", id: "qwen3-coder" }],
).find(e => e.slug === "local/qwen3-coder")?.comp_hash;

test("routed rows keep one value whichever native row is the template", () => {
expect(routedHash("3000")).toBe("opencodex");
expect(routedHash("2911")).toBe("opencodex");
expect(routedHash(undefined)).toBe("opencodex");
});

test("a row kept from disk during a provider outage drops a copied value", () => {
const merge = (catalogModels: readonly RawEntry[], routedEntries: readonly RawEntry[], degraded: boolean) =>
mergeCatalogEntriesFromObservedState({
catalogModels, routedEntries, baselineCatalogModels: [], baseline: new Map(), featured: [],
wsEnabled: false, template: null, disabledModels: new Set(), selectedModelsByProvider: new Map(),
gatheredProviderNames: new Set(["local"]),
degradedProviderNames: new Set(degraded ? ["local"] : []),
legacyCustomModelSlugs: new Set(), multiAgentMode: "default", multiAgentV2Enabled: false,
exactComboSlugs: new Set(), hasPhysicalComboProvider: false, includeNativeOpenAi: true,
accountBoundEntries: [],
policy: { ...CANONICAL_NATIVE_CATALOG_CONTENT_POLICY, warningPolicy: "suppress" },
});
const fresh = buildCatalogEntriesFromObservedState({
template: null, gptSlugs: [], goModels: [{ provider: "local", id: "qwen3-coder" }],
featured: [], modelPickerOrder: [], wsEnabled: false, multiAgentMode: "default",
exactComboSlugs: new Set(), accountSelectors: [], suppressedBareNativeSlugs: new Set(),
disabledNativeAccountSlugs: new Set(), multiAgentV2Enabled: false,
});
// A catalog written before the fix, where the routed row still carries a template's value,
// plus a row another tool wrote under the same provider. That one is not ours to change.
const saved = merge([], fresh, false).map(entry =>
entry.slug === "local/qwen3-coder" ? { ...entry, comp_hash: "3000" } : entry);
const foreign = {
...saved.find(entry => entry.slug === "local/qwen3-coder")!,
slug: "local/imported",
description: "Imported model",
};
const kept = merge([...saved, foreign], [], true);
const hash = (slug: string) => kept.find(entry => entry.slug === slug)?.comp_hash;
expect(hash("local/qwen3-coder")).toBe("opencodex");
expect(hash("local/imported")).toBe("3000");
});
});
1 change: 1 addition & 0 deletions tests/fixtures/test-layout-expected.json
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@
"catalog-opencode-go-context-window.test.ts": "codex-integration",
"catalog-remote-pull.test.ts": "codex-integration",
"catalog-retain-models.test.ts": "codex-integration",
"catalog-routed-comp-hash.test.ts": "codex-integration",
"catalog-seed-window-fill.test.ts": "codex-integration",
"catalog-slug-uniqueness-boundary.test.ts": "codex-integration",
"catalog-verbosity-default.test.ts": "codex-integration",
Expand Down
Loading