Skip to content

fix(codex): avoid image tool loops on vision turns and respect image-model providers#65061

Closed
zhulijin1991 wants to merge 2 commits into
openclaw:mainfrom
zhulijin1991:codex/codex-native-image-tool-fix
Closed

fix(codex): avoid image tool loops on vision turns and respect image-model providers#65061
zhulijin1991 wants to merge 2 commits into
openclaw:mainfrom
zhulijin1991:codex/codex-native-image-tool-fix

Conversation

@zhulijin1991

Copy link
Copy Markdown
Contributor

Summary

  • hide the dynamic image tool on Codex-native turns that already include inbound images and use a vision-capable model
  • inherit the configured agents.defaults.imageModel.primary provider when resolving bare image-model override ids
  • add focused regression tests for both paths

Problem

Codex-native image turns can stall when inbound images are already present in the turn input but the dynamic image tool is still exposed. Separately, bare overrides such as gpt-5.4-mini can resolve under the wrong provider when the configured image model lives under a non-default provider prefix.

Closes #65050

Testing

  • pnpm exec node --no-maglev node_modules/vitest/vitest.mjs run --config test/vitest/vitest.extensions.config.ts extensions/codex/src/app-server/run-attempt.vision-tools.test.ts
  • pnpm exec node --no-maglev node_modules/vitest/vitest.mjs run --config test/vitest/vitest.agents.config.ts src/agents/model-fallback.image-provider.test.ts
  • pnpm exec node --no-maglev node_modules/vitest/vitest.mjs run --config test/vitest/vitest.extensions.config.ts extensions/codex/src/app-server/run-attempt.test.ts
  • pnpm exec node --no-maglev node_modules/vitest/vitest.mjs run --config test/vitest/vitest.agents.config.ts src/agents/model-fallback.test.ts

@greptile-apps

greptile-apps Bot commented Apr 12, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes two related bugs: it hides the dynamic image tool on Codex-native turns where inbound images are already present and the model supports vision (preventing tool-loop stalls), and it corrects bare model-id resolution for runWithImageModelFallback so that override IDs like "gpt-5.4-mini" inherit the provider from the configured agents.defaults.imageModel.primary instead of always falling back to DEFAULT_PROVIDER. Both fixes include targeted regression tests.

Confidence Score: 5/5

This PR is safe to merge; both fixes are correct and targeted, with no P0 or P1 findings.

Both the vision-tool filtering and the provider-inheritance fix are logically sound. The only note is that resolveImageFallbackDefaultProvider also affects bare strings in the configured imageModel.fallbacks array (not just modelOverride), but this is almost certainly the right behaviour and there is no evidence of a real regression path. All remaining observations are P2.

No files require special attention.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/agents/model-fallback.ts
Line: 374-391

Comment:
**Side-effect on bare fallback IDs**

`resolveImageFallbackDefaultProvider` is passed as `defaultProvider` to the entire `resolveImageFallbackCandidates` call, so it affects not only the `modelOverride` path but also any bare (provider-less) strings in `agents.defaults.imageModel.fallbacks`. Before this change, those bare fallbacks resolved under `DEFAULT_PROVIDER`; after the change they resolve under the primary image model's provider.

For a config like `primary: "openai-codex/gpt-5.4", fallbacks: ["gpt-4o"]`, `"gpt-4o"` will now resolve to `openai-codex/gpt-4o` instead of the previous `openai/gpt-4o`. This is likely the intended behaviour (consistent provider across the image model group), but the PR description only mentions the `modelOverride` path and the existing test exercises fully-qualified fallback strings, so the bare-fallback case is untested. A test or a note in the description would confirm the intent.

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

Reviews (1): Last reviewed commit: "fix(image): respect configured provider ..." | Re-trigger Greptile

Comment on lines +374 to +391
function resolveImageFallbackDefaultProvider(cfg: OpenClawConfig | undefined): string {
const configuredPrimary = resolveAgentModelPrimaryValue(cfg?.agents?.defaults?.imageModel);
if (configuredPrimary?.trim()) {
const aliasIndex = buildModelAliasIndex({
cfg: cfg ?? {},
defaultProvider: DEFAULT_PROVIDER,
});
const resolved = resolveModelRefFromString({
raw: configuredPrimary,
defaultProvider: DEFAULT_PROVIDER,
aliasIndex,
});
if (resolved?.ref.provider) {
return resolved.ref.provider;
}
}
return DEFAULT_PROVIDER;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Side-effect on bare fallback IDs

resolveImageFallbackDefaultProvider is passed as defaultProvider to the entire resolveImageFallbackCandidates call, so it affects not only the modelOverride path but also any bare (provider-less) strings in agents.defaults.imageModel.fallbacks. Before this change, those bare fallbacks resolved under DEFAULT_PROVIDER; after the change they resolve under the primary image model's provider.

For a config like primary: "openai-codex/gpt-5.4", fallbacks: ["gpt-4o"], "gpt-4o" will now resolve to openai-codex/gpt-4o instead of the previous openai/gpt-4o. This is likely the intended behaviour (consistent provider across the image model group), but the PR description only mentions the modelOverride path and the existing test exercises fully-qualified fallback strings, so the bare-fallback case is untested. A test or a note in the description would confirm the intent.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/model-fallback.ts
Line: 374-391

Comment:
**Side-effect on bare fallback IDs**

`resolveImageFallbackDefaultProvider` is passed as `defaultProvider` to the entire `resolveImageFallbackCandidates` call, so it affects not only the `modelOverride` path but also any bare (provider-less) strings in `agents.defaults.imageModel.fallbacks`. Before this change, those bare fallbacks resolved under `DEFAULT_PROVIDER`; after the change they resolve under the primary image model's provider.

For a config like `primary: "openai-codex/gpt-5.4", fallbacks: ["gpt-4o"]`, `"gpt-4o"` will now resolve to `openai-codex/gpt-4o` instead of the previous `openai/gpt-4o`. This is likely the intended behaviour (consistent provider across the image model group), but the PR description only mentions the `modelOverride` path and the existing test exercises fully-qualified fallback strings, so the bare-fallback case is untested. A test or a note in the description would confirm the intent.

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

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e6882a29ba

ℹ️ About Codex in GitHub

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

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

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

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

Comment on lines +346 to +349
const visionFilteredTools = filterToolsForVisionInputs(allTools, {
modelHasVision,
hasInboundImages: (params.images?.length ?? 0) > 0,
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Badge Keep Codex dynamic tool catalog stable across turns

Filtering the tool list by params.images makes the dynamic tool catalog vary per turn, but thread resume logic treats any catalog fingerprint change as incompatible and starts a new thread (extensions/codex/src/app-server/thread-lifecycle.ts). In sessions that alternate between image and non-image prompts, this will repeatedly invalidate thread binding and lose prior conversation context, which is a behavioral regression introduced by this change.

Useful? React with 👍 / 👎.

@steipete

Copy link
Copy Markdown
Contributor

Thanks @zhulijin1991. I rebased and landed this as replacement PR #69551 because this fork is not maintainer-editable and the original branch conflicted with current main. I kept your two fixes, resolved the current test-hook conflict, added extra regression coverage, and will merge #69551 once CI is green.

@steipete

Copy link
Copy Markdown
Contributor

Replacement landed via #69551.

Main commit: 201bf85ce965720a011167e4287ab8e168dc04b4
Validation details: #69551 (comment)

Thanks again @zhulijin1991.

@steipete steipete closed this Apr 21, 2026
loongfay pushed a commit to YuanbaoTeam/openclaw that referenced this pull request Apr 21, 2026
lovewanwan pushed a commit to lovewanwan/openclaw that referenced this pull request Apr 28, 2026
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
zhonghe0615 pushed a commit to zhonghe0615/openclaw that referenced this pull request May 7, 2026
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
globalcaos pushed a commit to globalcaos/tinkerclaw that referenced this pull request May 13, 2026
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
jameslcowan pushed a commit to jameslcowan/openclaw that referenced this pull request Jun 2, 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 extensions: codex size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Codex-native vision turns can stall when inbound images are present and the dynamic image tool remains exposed

2 participants