Skip to content

fix(agents): unify Think/Reasoning defaults via shared resolveReasoningDefault helper#37940

Closed
who96 wants to merge 8 commits intoopenclaw:mainfrom
who96:codex/reasoning-default-status-fix
Closed

fix(agents): unify Think/Reasoning defaults via shared resolveReasoningDefault helper#37940
who96 wants to merge 8 commits intoopenclaw:mainfrom
who96:codex/reasoning-default-status-fix

Conversation

@who96
Copy link
Copy Markdown

@who96 who96 commented Mar 6, 2026

User Impact

Users asking /status or session_status about a reasoning-capable model could see contradictory state such as Think: medium while Reasoning: off when no explicit reasoningLevel was persisted. This fix makes both status surfaces report the same default reasoning state.

Problem

OpenClaw already had a consistent default for Think, but Reasoning still fell back to off in several reply/status paths whenever the session entry did not carry an explicit reasoningLevel. In practice that produced contradictory state cards such as Think: medium while Reasoning was treated as off. That mismatch then leaked into agent self-reports and confused operators about whether thinking was actually enabled.

What this PR changes

  • adds a shared resolveReasoningDefault() helper next to the existing thinking-default logic
  • threads that helper through the reply/model-selection pipeline so command/status entry points use the same defaulting behavior
  • updates the system prompt guidance so agents answering model/thinking-state questions are pushed to use session_status and to interpret enabled/disabled from Think rather than Reasoning
  • adds focused tests for the new defaulting behavior and the prompt guidance

Validation

  • corepack pnpm exec vitest run src/agents/system-prompt.test.ts src/auto-reply/status.test.ts src/auto-reply/reply/commands-status.test.ts src/auto-reply/reply/commands.test.ts src/auto-reply/reply/commands-approve.test.ts src/auto-reply/reply/commands-parsing.test.ts src/auto-reply/reply/commands-policy.test.ts
  • corepack pnpm build

AI Disclosure

  • This PR was AI-assisted (Claude + Codex review)
  • Degree of testing: fully tested (system-prompt, status, commands, reply-directives tests)
  • I understand what the code does
  • Bot review conversations have been addressed

@openclaw-barnacle openclaw-barnacle bot added agents Agent runtime and tooling size: S labels Mar 6, 2026
@who96 who96 marked this pull request as ready for review March 6, 2026 14:52
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 6, 2026

Greptile Summary

This PR unifies the Think/Reasoning default-resolution logic by introducing a shared resolveReasoningDefault() helper that checks operator per-model config before falling back to the bundled model catalog — eliminating the state inconsistency where /status could show Think: medium while Reasoning was reported as off.

Key changes:

  • resolveReasoningDefault helper (src/agents/model-selection.ts): now accepts cfg and checks cfg.models.providers[provider].models[*].reasoning (boolean) first, only consulting the catalog as a secondary signal. This correctly mirrors the priority order of resolveThinkingDefault.
  • Async memoization (src/auto-reply/reply/model-selection.ts): resolveDefaultReasoningLevel is refactored with a reasoningLevelPromise guard to deduplicate concurrent loadModelCatalog calls before a value is cached, improving on the previous uncached async design.
  • Dead-code fix (src/auto-reply/reply/directive-handling.levels.ts + get-reply-directives-apply.ts): currentReasoningLevel no longer hardcodes "off" as the fallback; the ?? (await modelState.resolveDefaultReasoningLevel()) branch in the calling code is now actually reachable.
  • Sync status path (src/auto-reply/status.ts + src/agents/tools/session-status-tool.ts): buildStatusMessage now accepts a catalog parameter and calls resolveReasoningDefault as its last fallback. The session-status tool loads the catalog (with a graceful try/catch) before calling buildStatusMessage, so both the sync and async status surfaces produce the same default.
  • Threading: resolveDefaultReasoningLevel is propagated end-to-end through getReplyFromConfighandleInlineActionshandleCommandsbuildStatusReply, covering every surface that can display a status card.
  • System prompt guidance: agents are instructed to call session_status for model/thinking-state questions and to read Think/thinkingLevel, not Reasoning.
  • All three previously raised review issues (catalog vs. config priority, missing catalog on the sync path, dead-code fallback) have been addressed in this revision.

Confidence Score: 5/5

  • This PR is safe to merge — changes are narrowly scoped to default-resolution logic with no write paths or security surfaces affected.
  • All three issues raised in prior review rounds have been addressed: config-before-catalog priority is now correct, buildStatusMessage receives the catalog on the sync path, and the dead-code fallback in get-reply-directives-apply.ts is now reachable. The refactored reasoningLevelPromise deduplication is a strict improvement over the previous design. Tests cover config-priority, catalog-only, and end-to-end status output for both the /status command path and the session_status tool. No regressions were found across the 22 changed files.
  • No files require special attention.

Last reviewed commit: 69a12d8

Comment on lines +383 to +385
const reasoningLevel =
args.resolvedReasoning ??
(args.config ? resolveReasoningDefault({ cfg: args.config, provider, model }) : "off");
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.

resolveReasoningDefault called without catalog

buildStatusMessage calls resolveReasoningDefault without the catalog parameter. The function first checks the catalog for a matching entry with reasoning === true, then falls back to the user-config path (cfg.models.providers.[p].models.[m].reasoning). Since no catalog is passed here, the catalog-based detection is silently skipped.

This means: for any reasoning-capable model whose capability is declared only in the bundled model catalog (but not explicitly set in the operator's config), buildStatusMessage will display Reasoning: off even when the model is actually a reasoning model. The /status command path (buildStatusReplyresolveDefaultReasoningLevel) correctly loads the catalog and avoids this problem; this synchronous path does not.

The new test in status.test.ts only covers the case where reasoning: true is set in the user config, so it doesn't catch this gap.

A straightforward fix is to add an optional catalog?: ModelCatalogEntry[] field to StatusArgs and thread it through:

// StatusArgs
catalog?: ModelCatalogEntry[];

// buildStatusMessage
const reasoningLevel =
  args.resolvedReasoning ??
  (args.config
    ? resolveReasoningDefault({ cfg: args.config, provider, model, catalog: args.catalog })
    : "off");

Callers that already have a catalog available (e.g. the session_status tool) can then pass it in, and the synchronous path will behave consistently with the async command path.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/auto-reply/status.ts
Line: 383-385

Comment:
**`resolveReasoningDefault` called without catalog**

`buildStatusMessage` calls `resolveReasoningDefault` without the `catalog` parameter. The function first checks the catalog for a matching entry with `reasoning === true`, then falls back to the user-config path (`cfg.models.providers.[p].models.[m].reasoning`). Since no catalog is passed here, the catalog-based detection is silently skipped.

This means: for any reasoning-capable model whose capability is declared only in the bundled model catalog (but not explicitly set in the operator's config), `buildStatusMessage` will display `Reasoning: off` even when the model is actually a reasoning model. The `/status` command path (`buildStatusReply``resolveDefaultReasoningLevel`) correctly loads the catalog and avoids this problem; this synchronous path does not.

The new test in `status.test.ts` only covers the case where `reasoning: true` is set in the user config, so it doesn't catch this gap.

A straightforward fix is to add an optional `catalog?: ModelCatalogEntry[]` field to `StatusArgs` and thread it through:

```typescript
// StatusArgs
catalog?: ModelCatalogEntry[];

// buildStatusMessage
const reasoningLevel =
  args.resolvedReasoning ??
  (args.config
    ? resolveReasoningDefault({ cfg: args.config, provider, model, catalog: args.catalog })
    : "off");
```

Callers that already have a catalog available (e.g. the `session_status` tool) can then pass it in, and the synchronous path will behave consistently with the async command path.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Addressed in a63ceac155acfbb9725922526b215fd39e24b2c1. That commit threads catalog-backed reasoning defaults through the synchronous status and session_status path so the sync surface now matches the async command path.

@who96
Copy link
Copy Markdown
Author

who96 commented Mar 6, 2026

@greptile-apps review - New commit 9996058 addresses the catalog parameter gap identified in the initial review. buildStatusMessage now accepts an optional catalog field via StatusArgs and passes it to resolveReasoningDefault, covering the synchronous path. A catalog-only reasoning model test was also added.

Comment on lines +428 to +438
const candidate = params.catalog?.find(
(entry) => entry.provider === params.provider && entry.id === params.model,
);
if (candidate?.reasoning === true) {
return "on";
}
const configured =
params.cfg?.models?.providers?.[params.provider]?.models?.find(
(entry) => entry.id === params.model,
)?.reasoning ?? false;
return configured ? "on" : "off";
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.

Catalog takes priority over operator config — user reasoning: false silently ignored

resolveReasoningDefault checks the bundled catalog first. If the catalog has an entry with reasoning: true, the function returns "on" immediately and never consults the operator's per-model reasoning flag. This means an operator who explicitly sets reasoning: false in models.providers.[provider].models to opt a specific model out of the reasoning default will have that setting silently ignored whenever the model also appears in the catalog with reasoning: true.

This is the opposite priority of resolveThinkingDefault, which checks cfg.agents.defaults.thinkingDefault (i.e., operator config) first and only falls through to the catalog as a secondary signal.

For consistent and predictable behaviour, operator-declared per-model config should win over catalog defaults:

export function resolveReasoningDefault(params: {
  cfg?: OpenClawConfig;
  provider: string;
  model: string;
  catalog?: ModelCatalogEntry[];
}): "on" | "off" {
  // User config takes priority over catalog defaults
  const configuredEntry =
    params.cfg?.models?.providers?.[params.provider]?.models?.find(
      (entry) => entry.id === params.model,
    );
  if (configuredEntry !== undefined && "reasoning" in configuredEntry) {
    return configuredEntry.reasoning ? "on" : "off";
  }
  const candidate = params.catalog?.find(
    (entry) => entry.provider === params.provider && entry.id === params.model,
  );
  if (candidate?.reasoning === true) {
    return "on";
  }
  return "off";
}

The existing test in status.test.ts ("shows reasoning as on for reasoning-capable models when not explicitly overridden") only covers the case where the catalog entry is absent and user config has reasoning: true, so the conflict scenario is not currently caught by the test suite.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/model-selection.ts
Line: 428-438

Comment:
**Catalog takes priority over operator config — user `reasoning: false` silently ignored**

`resolveReasoningDefault` checks the bundled catalog **first**. If the catalog has an entry with `reasoning: true`, the function returns `"on"` immediately and never consults the operator's per-model `reasoning` flag. This means an operator who explicitly sets `reasoning: false` in `models.providers.[provider].models` to opt a specific model out of the reasoning default will have that setting silently ignored whenever the model also appears in the catalog with `reasoning: true`.

This is the **opposite priority** of `resolveThinkingDefault`, which checks `cfg.agents.defaults.thinkingDefault` (i.e., operator config) first and only falls through to the catalog as a secondary signal.

For consistent and predictable behaviour, operator-declared per-model config should win over catalog defaults:

```
export function resolveReasoningDefault(params: {
  cfg?: OpenClawConfig;
  provider: string;
  model: string;
  catalog?: ModelCatalogEntry[];
}): "on" | "off" {
  // User config takes priority over catalog defaults
  const configuredEntry =
    params.cfg?.models?.providers?.[params.provider]?.models?.find(
      (entry) => entry.id === params.model,
    );
  if (configuredEntry !== undefined && "reasoning" in configuredEntry) {
    return configuredEntry.reasoning ? "on" : "off";
  }
  const candidate = params.catalog?.find(
    (entry) => entry.provider === params.provider && entry.id === params.model,
  );
  if (candidate?.reasoning === true) {
    return "on";
  }
  return "off";
}
```

The existing test in `status.test.ts` ("shows reasoning as on for reasoning-capable models when not explicitly overridden") only covers the case where the catalog entry is absent and user config has `reasoning: true`, so the conflict scenario is not currently caught by the test suite.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Addressed in a63ceac155acfbb9725922526b215fd39e24b2c1. That change makes explicit operator config win before catalog defaults, so an explicit reasoning: false override is preserved.

@who96
Copy link
Copy Markdown
Author

who96 commented Mar 6, 2026

@greptile-apps review - New commit a63ceac addresses the priority ordering issue: resolveReasoningDefault now checks operator config first, then falls back to catalog, matching resolveThinkingDefault's priority. Added test for config reasoning:false overriding catalog reasoning:true.

@who96
Copy link
Copy Markdown
Author

who96 commented Mar 12, 2026

@scoootscooob Would appreciate a quick look when you have a moment — happy to adjust if the approach needs changes. Thanks!

@who96 who96 force-pushed the codex/reasoning-default-status-fix branch 2 times, most recently from fd998fa to 0888164 Compare March 13, 2026 01:50
@openclaw-barnacle openclaw-barnacle bot removed the app: macos App: macos label Mar 13, 2026
@who96
Copy link
Copy Markdown
Author

who96 commented Mar 13, 2026

Note: the 3 failing checks are all the same pre-existing loader.test.ts ANSI sanitization issue — also failing on other unrelated PRs (#44397, #44390). Filed as #44536. Not caused by this PR's changes.

@who96
Copy link
Copy Markdown
Author

who96 commented Mar 14, 2026

@tyler6204 @hydro13 Friendly ping — this PR has been open for 9 days with no human review. Bot reviews (Greptile 4/5) indicate safe to merge. Happy to adjust if the approach needs changes. Thanks!

@who96
Copy link
Copy Markdown
Author

who96 commented Mar 14, 2026

@greptileai review

Comment on lines +163 to +164
const currentReasoningLevel =
resolvedCurrentReasoningLevel ?? (await modelState.resolveDefaultReasoningLevel());
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.

resolveDefaultReasoningLevel fallback is unreachable dead code

resolveCurrentDirectiveLevels (in directive-handling.levels.ts) returns currentReasoningLevel typed as ReasoningLevel — never undefined. When the session has no explicit reasoningLevel, it fills in "off" as the hardcoded default (line 34–35 of directive-handling.levels.ts):

// directive-handling.levels.ts – BEFORE this PR
const currentReasoningLevel =
  (params.sessionEntry?.reasoningLevel as ReasoningLevel | undefined) ?? "off";

Because the return type is ReasoningLevel (a union of non-empty strings, all truthy), the ?? guard on the consuming side:

const currentReasoningLevel =
  resolvedCurrentReasoningLevel ?? (await modelState.resolveDefaultReasoningLevel());

…can never evaluate the right-hand branch — "off" is a truthy string, not null/undefined. resolveDefaultReasoningLevel() is dead code here.

For the directive-only path (an inline +status directive embedded in a regular message), sessions without an explicit reasoningLevel will therefore always display Reasoning: off, even when the model catalog marks the model as reasoning: true. This is exactly the bug the PR intends to fix, but the fix doesn't reach this code path.

The root fix needs to land in resolveCurrentDirectiveLevels — change its return type to currentReasoningLevel: ReasoningLevel | undefined and remove the ?? "off" default there, then let the caller apply the catalog fallback:

// directive-handling.levels.ts
const currentReasoningLevel =
  params.sessionEntry?.reasoningLevel as ReasoningLevel | undefined;
// return it as ReasoningLevel | undefined — callers apply the catalog default

With that change the ?? guard in get-reply-directives-apply.ts would fire correctly when the session has no persisted value, and resolveDefaultReasoningLevel() would supply the catalog-backed default.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/auto-reply/reply/get-reply-directives-apply.ts
Line: 163-164

Comment:
**`resolveDefaultReasoningLevel` fallback is unreachable dead code**

`resolveCurrentDirectiveLevels` (in `directive-handling.levels.ts`) returns `currentReasoningLevel` typed as `ReasoningLevel` — never `undefined`. When the session has no explicit `reasoningLevel`, it fills in `"off"` as the hardcoded default (line 34–35 of `directive-handling.levels.ts`):

```ts
// directive-handling.levels.ts – BEFORE this PR
const currentReasoningLevel =
  (params.sessionEntry?.reasoningLevel as ReasoningLevel | undefined) ?? "off";
```

Because the return type is `ReasoningLevel` (a union of non-empty strings, all truthy), the `??` guard on the consuming side:

```ts
const currentReasoningLevel =
  resolvedCurrentReasoningLevel ?? (await modelState.resolveDefaultReasoningLevel());
```

…can **never** evaluate the right-hand branch — `"off"` is a truthy string, not `null`/`undefined`. `resolveDefaultReasoningLevel()` is dead code here.

For the directive-only path (an inline `+status` directive embedded in a regular message), sessions without an explicit `reasoningLevel` will therefore always display `Reasoning: off`, even when the model catalog marks the model as `reasoning: true`. This is exactly the bug the PR intends to fix, but the fix doesn't reach this code path.

The root fix needs to land in `resolveCurrentDirectiveLevels` — change its return type to `currentReasoningLevel: ReasoningLevel | undefined` and remove the `?? "off"` default there, then let the caller apply the catalog fallback:

```ts
// directive-handling.levels.ts
const currentReasoningLevel =
  params.sessionEntry?.reasoningLevel as ReasoningLevel | undefined;
// return it as ReasoningLevel | undefined — callers apply the catalog default
```

With that change the `??` guard in `get-reply-directives-apply.ts` would fire correctly when the session has no persisted value, and `resolveDefaultReasoningLevel()` would supply the catalog-backed default.

How can I resolve this? If you propose a fix, please make it concise.

expect(normalizeModelSelection(null)).toBeUndefined();
expect(normalizeModelSelection(42)).toBeUndefined();
});

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.

resolveReasoningDefault tests nested inside wrong describe block

The new describe("resolveReasoningDefault", ...) block is placed as a nested child of describe("normalizeModelSelection", ...). These are unrelated utilities, so test output (and IDE tree views) will show the new tests under the normalizeModelSelection suite, making failures harder to locate.

Consider promoting it to a top-level describe, or nesting it under an existing block that is specifically about model-selection helpers (there are several at the top of the file):

Suggested change
});
describe("resolveReasoningDefault", () => {
it("uses explicit config reasoning false before catalog reasoning true", () => {
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/model-selection.test.ts
Line: 852

Comment:
**`resolveReasoningDefault` tests nested inside wrong describe block**

The new `describe("resolveReasoningDefault", ...)` block is placed as a nested child of `describe("normalizeModelSelection", ...)`. These are unrelated utilities, so test output (and IDE tree views) will show the new tests under the `normalizeModelSelection` suite, making failures harder to locate.

Consider promoting it to a top-level describe, or nesting it under an existing block that is specifically about model-selection helpers (there are several at the top of the file):

```suggestion
});

describe("resolveReasoningDefault", () => {
  it("uses explicit config reasoning false before catalog reasoning true", () => {
```

How can I resolve this? If you propose a fix, please make it concise.

@who96 who96 force-pushed the codex/reasoning-default-status-fix branch from 6bffec6 to 7117163 Compare March 14, 2026 19:04
@openclaw-barnacle openclaw-barnacle bot added the gateway Gateway runtime label Mar 14, 2026
@who96
Copy link
Copy Markdown
Author

who96 commented Mar 14, 2026

Both issues from the last review have been addressed:

  1. Dead-code fallback in directive-only path — Removed the ?? "off" hardcoded default in resolveCurrentDirectiveLevels (directive-handling.levels.ts), widened the return type to ReasoningLevel | undefined. The ?? fallback in get-reply-directives-apply.ts now correctly reaches resolveDefaultReasoningLevel() when no session value is set.

  2. Test describe block mis-nested — Promoted describe("resolveReasoningDefault") to a top-level suite, no longer nested under describe("normalizeModelSelection").

@greptileai review

@who96
Copy link
Copy Markdown
Author

who96 commented Mar 14, 2026

Both issues from the last review have been addressed:

  1. Dead-code fallback — Removed the hardcoded default in directive-handling.levels.ts, widened return type to ReasoningLevel | undefined. The fallback in get-reply-directives-apply.ts now correctly reaches resolveDefaultReasoningLevel().

  2. Test describe mis-nested — Promoted resolveReasoningDefault to a top-level suite.

@greptileai review

Comment on lines +406 to +408
const resolveDefaultReasoningLevel = async (): Promise<"on" | "off"> => {
if (defaultReasoningLevel) {
return defaultReasoningLevel;
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.

Caching guard won't short-circuit for "off" — but only on first call

if (defaultReasoningLevel) is truthy for both "on" and "off" (non-empty strings), so the cache works correctly once a value has been stored. However, because the result of resolveReasoningDefault is synchronous, two concurrent await resolveDefaultReasoningLevel() calls that both see defaultReasoningLevel === undefined will both fall through and call loadModelCatalog a second time before the first write lands. This is an inherent race in the memoization pattern (also present in resolveDefaultThinkingLevel), but it's worth keeping in mind if loadModelCatalog ever becomes expensive or stateful.

No change is required for correctness; the worst case is a redundant catalog load that produces the same value.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/auto-reply/reply/model-selection.ts
Line: 406-408

Comment:
**Caching guard won't short-circuit for `"off"` — but only on first call**

`if (defaultReasoningLevel)` is truthy for both `"on"` and `"off"` (non-empty strings), so the cache works correctly once a value has been stored. However, because the result of `resolveReasoningDefault` is synchronous, two concurrent `await resolveDefaultReasoningLevel()` calls that both see `defaultReasoningLevel === undefined` will both fall through and call `loadModelCatalog` a second time before the first write lands. This is an inherent race in the memoization pattern (also present in `resolveDefaultThinkingLevel`), but it's worth keeping in mind if `loadModelCatalog` ever becomes expensive or stateful.

No change is required for correctness; the worst case is a redundant catalog load that produces the same value.

How can I resolve this? If you propose a fix, please make it concise.

@who96
Copy link
Copy Markdown
Author

who96 commented Mar 14, 2026

Addressed both remaining observations:

  1. Dead catalog branch: Removed the unreachable entry.provider === key second || arm and the now-unused key variable in resolveReasoningDefault.

  2. Memoization race: resolveDefaultReasoningLevel now caches the in-flight Promise so concurrent callers share the same loadModelCatalog call.

@greptileai review

@who96
Copy link
Copy Markdown
Author

who96 commented Mar 14, 2026

@tyler6204 @hydro13 Friendly ping — this PR has been open for 8 days with no human review. All CI checks pass and Greptile gives 5/5 confidence. Would appreciate a review when you have a moment. Thanks!

@who96 who96 force-pushed the codex/reasoning-default-status-fix branch from 69a12d8 to 605a0bc Compare March 15, 2026 13:35
@openclaw-barnacle openclaw-barnacle bot removed the gateway Gateway runtime label Mar 15, 2026
@who96
Copy link
Copy Markdown
Author

who96 commented Mar 15, 2026

Rebased onto latest main (c4265a5) to resolve merge conflicts. Two earlier commits dropped as already obsoleted by upstream changes. All 5 substantive fix commits preserved. CI re-running.

@who96
Copy link
Copy Markdown
Author

who96 commented Mar 16, 2026

Addressing remaining bot review conversations:


Re: resolveDefaultReasoningLevel fallback is unreachable dead code (Greptile, get-reply-directives-apply.ts)
This is the most substantive finding. Greptile correctly identified that directive-handling.levels.ts returns "off" (truthy) as the hardcoded default, so the ?? guard in the caller never fires, making resolveDefaultReasoningLevel() dead code on that path. The proper fix is to remove the ?? "off" default from resolveCurrentDirectiveLevels and let the caller apply the catalog-backed default instead. Will investigate and fix.

Re: resolveReasoningDefault tests nested inside wrong describe block (Greptile, model-selection.test.ts)
Agreed — the describe("resolveReasoningDefault", ...) block was accidentally nested inside describe("normalizeModelSelection", ...). Will promote it to a top-level describe so test output and IDE tree views correctly show it as an independent suite.

Re: Caching guard won't short-circuit for "off" (Greptile, model-selection.ts)
Acknowledged — Greptile correctly notes that the memoization pattern has an inherent race when two concurrent calls both see undefined before the first write lands. However, as stated in the review, no change is required for correctness since the worst case is a redundant catalog load that produces the same value. The same pattern exists in resolveDefaultThinkingLevel. No action needed.

@who96 who96 force-pushed the codex/reasoning-default-status-fix branch 2 times, most recently from 6b1d7ff to ff8ab83 Compare March 17, 2026 07:24
who96 added 8 commits March 17, 2026 18:33
…evel | undefined

Remove the hardcoded ?? "off" fallback in directive-handling.levels.ts so
that the resolveDefaultReasoningLevel() fallback in get-reply-directives-apply.ts
is actually reachable for sessions without an explicit reasoningLevel.

Also promote the resolveReasoningDefault test suite to a top-level describe
block so test output correctly attributes failures.
1. resolveReasoningDefault: remove unreachable entry.provider === key
   branch and the now-unused key variable.

2. resolveDefaultReasoningLevel: cache the in-flight Promise so
   concurrent callers share the same loadModelCatalog call instead
   of each triggering a redundant load.
@who96 who96 force-pushed the codex/reasoning-default-status-fix branch from ff8ab83 to ac9f378 Compare March 17, 2026 10:33
@who96
Copy link
Copy Markdown
Author

who96 commented Mar 18, 2026

Closing: the core goals of this PR (unifying Think/Reasoning defaults) have been fully superseded by upstream commits including #22513, #30439, and the fast-mode refactor (d5bffcda). The affected files have been substantially rewritten, making this PR obsolete.

@who96 who96 closed this Mar 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant