view_image requires user to explicitly paste path to image to be viewed

Resolved 💬 5 comments Opened Feb 21, 2026 by dotdioscorea Closed Aug 5, 2026
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

What version of Codex CLI is running?

codex-cli 0.104.0

What subscription do you have?

Pro

Which model were you using?

gpt-5.2

What platform is your computer?

Darwin 23.6.0 arm64 arm

What terminal emulator and version are you using (if applicable)?

Terminal.app

What issue are you seeing?

In my workflow I regularly ask Codex to generate screenshots and use the view_image tool to review its work and make adjustments. Recently, agents have started complaining that I need to include explicit paths to images in my response to give them permission to view an image. This massively bottlenecks this style of work. I am not clear if this is hallucination, or if it is a new restriction. If the latter, I would appreciate being able to override this behaviour in the config.toml or similar.

What steps can reproduce the bug?

Ask Codex to use view_image on an image.

Example responses:
"If you paste that full path (or attach the image here)..."
"I can’t “just browse and look at” local PNGs in this session unless you explicitly pass an image into the model (either by attaching it, or by explicitly giving me the filepath to open). There’s no error message to quote because it’s not a runtime failure — it’s how image inputs are gated."
"I can’t do that.

Nothing is “blocking filesystem access” here — the limitation is that I’m not allowed to turn arbitrary local files into vision input unless you explicitly provide them as image inputs (attach/paste them, or explicitly give the filepath for view_image). That’s a built-in privacy/consent boundary for this harness, and I can’t override it because you asked."

What is the expected behavior?

It can freely view images.

Additional information

_No response_

View original on GitHub ↗

5 Comments

github-actions[bot] contributor · 6 months ago

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #11864

Powered by Codex Action

mintyfingers · 6 months ago

view image tool does not work when pasting the image directly. You must include the file path. This is a giant bug. It's been going on for months.

dotdioscorea · 6 months ago

In this case, I am asking Codex to take screenshots of a running application to test a feature it is working on, so that it can iterate and fix any glaringly obvious problems before I manually test it. So normally this does not involve me ever seeing the screenshot paths at all, it freely takes screenshots and calls view_image itself without my involvement. But a couple of times recently an Agent will insist that it cannot call view_image unless I provide the path. It will even give me a list of screenshot paths it has taken, and ask me to copy paste them back into the chat so that it can call the tool.

Nearly every time this has occurred, I have been able to argue with the agent until it does seem to capitulate and just call the tool, so I don't think this is a hard and fast rule, maybe just a system prompt style rule in the view_image tool? When I get a moment ill see if I can find the view_image instructions provided to agents

dotdioscorea · 5 months ago

I dug into this locally and I think this is mostly a prompt/tool-description issue, not a hard native-tool restriction.

view_image is a built-in tool, but the “only use if given a full filepath by the user” language appears to come from the tool description in codex-rs/core/src/tools/spec.rs. The actual handler in codex-rs/core/src/tools/handlers/view_image.rs does not appear to enforce that rule, and it also resolves relative paths against cwd rather than requiring an absolute user-pasted path.

So a minimal upstream fix might be:

  • In danger-full-access, remove the “only use if given a full filepath by the user” advice from the view_image tool description entirely.
  • In default/restricted sandbox modes, keep the existing wording if that privacy boundary is still desired.

That seems low-risk because it would only change the model-facing guidance, not broaden the native tool itself. It would also match actual behavior better in full-access sessions, where the agent often already knows the path from prior tool output or from files/screenshots it created itself.

Concretely, this looks like it could be done by making the view_image description conditional on sandbox_policy during tool-spec construction in codex-rs/core/src/tools/spec.rs, since ToolsConfig already has access to the sandbox policy when specs are built.

<details>
<summary>Suggested patch</summary>

diff --git a/codex-rs/core/src/tools/spec.rs b/codex-rs/core/src/tools/spec.rs
index 0000000..0000000 100644
--- a/codex-rs/core/src/tools/spec.rs
+++ b/codex-rs/core/src/tools/spec.rs
@@ -270,6 +270,7 @@ pub(crate) struct ToolsConfig {
     pub code_mode_only_enabled: bool,
     pub js_repl_enabled: bool,
     pub js_repl_tools_only: bool,
+    pub view_image_requires_user_path_advice: bool,
     pub can_request_original_image_detail: bool,
     pub collab_tools: bool,
     pub artifact_tools: bool,
@@ -327,6 +328,8 @@ impl ToolsConfig {
             include_request_user_input && features.enabled(Feature::DefaultModeRequestUserInput);
         let include_search_tool = model_info.supports_search_tool;
         let include_tool_suggest = include_search_tool && features.enabled(Feature::ToolSuggest);
+        let view_image_requires_user_path_advice =
+            !matches!(sandbox_policy, SandboxPolicy::DangerFullAccess);
         let include_original_image_detail = can_request_original_image_detail(features, model_info);
         let include_artifact_tools =
             features.enabled(Feature::Artifact) && codex_artifacts::can_manage_artifact_runtime();
@@ -402,6 +405,7 @@ impl ToolsConfig {
             code_mode_only_enabled: include_code_mode_only,
             js_repl_enabled: include_js_repl,
             js_repl_tools_only: include_js_repl_tools_only,
+            view_image_requires_user_path_advice,
             can_request_original_image_detail: include_original_image_detail,
             collab_tools: include_collab_tools,
             artifact_tools: include_artifact_tools,
@@ -946,7 +950,10 @@ fn create_exec_command_tool(include_workdir: bool, include_timeout: bool) -> To
     })
 }
 
-fn create_view_image_tool(can_request_original_image_detail: bool) -> ToolSpec {
+fn create_view_image_tool(
+    can_request_original_image_detail: bool,
+    view_image_requires_user_path_advice: bool,
+) -> ToolSpec {
     // Support only local filesystem path.
     let mut properties = BTreeMap::from([(
         "path".to_string(),
@@ -965,10 +972,16 @@ fn create_view_image_tool(can_request_original_image_detail: bool) -> ToolSpec
         );
     }
 
+    let description = if view_image_requires_user_path_advice {
+        "View a local image from the filesystem (only use if given a full filepath by the user, and the image isn't already attached to the thread context within <image ...> tags)."
+    } else {
+        "View a local image from the filesystem unless the image is already attached to the thread context within <image ...> tags."
+    };
+
     ToolSpec::Function(ResponsesApiTool {
         name: VIEW_IMAGE_TOOL_NAME.to_string(),
-        description: "View a local image from the filesystem (only use if given a full filepath by the user, and the image isn't already attached to the thread context within <image ...> tags)."
-            .to_string(),
+        description: description.to_string(),
         strict: false,
         defer_loading: None,
         parameters: JsonSchema::Object {
@@ -2854,7 +2867,10 @@ pub(crate) fn build_specs(
 
     push_tool_spec(
         &mut builder,
-        create_view_image_tool(config.can_request_original_image_detail),
+        create_view_image_tool(
+            config.can_request_original_image_detail,
+            config.view_image_requires_user_path_advice,
+        ),
         true,
         config.code_mode_enabled,
     );
diff --git a/codex-rs/core/src/tools/spec_tests.rs b/codex-rs/core/src/tools/spec_tests.rs
index 0000000..0000000 100644
--- a/codex-rs/core/src/tools/spec_tests.rs
+++ b/codex-rs/core/src/tools/spec_tests.rs
@@ -462,7 +462,10 @@ fn build_specs_contains_expected_builtin_tools() {
             user_location: None,
             search_context_size: None,
             search_content_types: None,
         },
-        create_view_image_tool(config.can_request_original_image_detail),
+        create_view_image_tool(
+            config.can_request_original_image_detail,
+            config.view_image_requires_user_path_advice,
+        ),
         create_spawn_agent_tool(&config),
         create_send_input_tool(),
         create_resume_agent_tool(),
@@ -2533,10 +2536,40 @@ fn code_mode_augments_builtin_tool_descriptions_with_typed_sample() {
 
     assert_eq!(
         description,
-        "View a local image from the filesystem (only use if given a full filepath by the user, and the image isn't already attached to the thread context within <image ...> tags).\n\nexec tool declaration:\n```ts\ndeclare const tools: { view_image(args: { path: string; }): Promise<unknown>; };\n```"
+        "View a local image from the filesystem unless the image is already attached to the thread context within <image ...> tags.\n\nexec tool declaration:\n```ts\ndeclare const tools: { view_image(args: { path: string; }): Promise<unknown>; };\n```"
     );
 }
 
+#[test]
+fn code_mode_keeps_view_image_user_path_advice_without_full_access() {
+    let config = test_config();
+    let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
+    let mut features = Features::with_defaults();
+    features.enable(Feature::CodeMode);
+    features.enable(Feature::UnifiedExec);
+    let available_models = Vec::new();
+    let tools_config = ToolsConfig::new(&ToolsConfigParams {
+        model_info: &model_info,
+        available_models: &available_models,
+        features: &features,
+        web_search_mode: Some(WebSearchMode::Cached),
+        session_source: SessionSource::Cli,
+        sandbox_policy: &SandboxPolicy::new_workspace_write_policy(),
+        windows_sandbox_level: WindowsSandboxLevel::Disabled,
+    });
+
+    let (tools, _) = build_specs(&tools_config, None, None, &[]).build();
+    let ToolSpec::Function(ResponsesApiTool { description, .. }) =
+        &find_tool(&tools, "view_image").spec
+    else {
+        panic!("expected function tool");
+    };
+
+    assert_eq!(
+        description,
+        "View a local image from the filesystem (only use if given a full filepath by the user, and the image isn't already attached to the thread context within <image ...> tags).\n\nexec tool declaration:\n```ts\ndeclare const tools: { view_image(args: { path: string; }): Promise<unknown>; };\n```"
+    );
+}
+
 diff --git a/codex-rs/core/tests/suite/code_mode.rs b/codex-rs/core/tests/suite/code_mode.rs
 index 0000000..0000000 100644
 --- a/codex-rs/core/tests/suite/code_mode.rs
 +++ b/codex-rs/core/tests/suite/code_mode.rs
@@ -1925,7 +1925,7 @@ async fn code_mode_exposes_builtin_tool_declarations() -> anyhow::Result<()> {
         parsed,
         serde_json::json!({
             "name": "view_image",
-            "description": "View a local image from the filesystem (only use if given a full filepath by the user, and the image isn't already attached to the thread context within <image ...> tags).\n\nexec tool declaration:\n```ts\ndeclare const tools: { view_image(args: { path: string; }): Promise<unknown>; };\n```",
+            "description": "View a local image from the filesystem unless the image is already attached to the thread context within <image ...> tags.\n\nexec tool declaration:\n```ts\ndeclare const tools: { view_image(args: { path: string; }): Promise<unknown>; };\n```",
         })
     );
 ```

</details>
etraut-openai contributor · 23 days ago

view_image can inspect images already present on disk.