☰
Codex Desktop Markdown preview 不渲染相对路径本地图片:把 settings 改到 TaoToken 的排查记录
2026/10/7 23:52:56 网站建设 项目流程

1. Codex Desktop 预览裂图:相对路径本地图片为什么加载不出来

Codex Desktop 的 Markdown preview 不渲染相对路径本地图片,这个问题的典型表现是:你在.md里写了![架构图](./images/arch.png),编辑区一切正常,切到预览面板却是一张裂图,控制台里躺着Failed to load resource或者Not allowed to load local resource。同一份文件丢进别的编辑器打开,图片好端端地显示出来,唯独 Codex Desktop 的预览器不认。

这个现象说明问题不在图片本身,而在「相对谁解析」。相对路径./images/arch.png本身不是一个完整地址,它必须挂在一个基准地址(base URL)上才能算出真正的绝对位置。预览器把 Markdown 渲染成 HTML 后塞进一个内嵌 WebView,如果这个 WebView 加载的是http://localhost:port/preview这类虚拟地址,那么相对路径的基址就变成了http://localhost:port/,跟你的.md文件真实目录毫无关系,图片自然找不到。

适合读这篇的人:用 Codex Desktop 写技术文档、维护项目 README、做本地知识库的开发者。你会拿到可复制的 settings 片段、逐步验证动作,以及一套从路径解析、工作区根目录到渲染器配置的排查链路。我试过把同一份 md 在三种基址下跑一遍,结论很直接——基址一错,相对路径就指向了不存在的地方。

先把核心检索词摆清楚:Codex Desktop 的 Markdown preview 依赖 WebView 渲染,local images 能否显示取决于 relative paths 的解析基址是否正确。搞懂这一条,后面所有配置都有方向。

2. TaoToken 前置:把模型接入配置收拢到一处

在动手改预览配置之前,先把模型接入这条链路理顺,因为 Codex Desktop 里很多行为受 settings 影响,而 settings 里往往同时管着模型端点和渲染选项。TaoToken 在这里的角色是统一的模型接入层,官网地址是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 入口是 https://taotoken.net/api 。

你需要先拿到一个 API Key,入口在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite 。拿到之后,Codex Desktop 的模型配置通常写在auth.json或对应的 settings 文件里,三件套是 Base URL、Key、Model ID,缺一不可。Base URL 填https://taotoken.net/api,Key 填你申请到的那串,Model ID 按你实际要用的模型填。

这里要强调一个容易混的点:模型接入配置和 Markdown 预览配置是两套东西,但它们在 Codex Desktop 里可能落在同一个 settings 文件的不同字段下。很多人改预览问题时顺手把模型配置也动了,结果预览没修好,模型请求先 401 了。所以我的建议是分两步走——先把模型接入验证通过,再单独处理预览的路径解析。

验证模型接入是否正常,可以用模型对话页面直接测: https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite 。如果那边能正常出结果,说明 Base URL 和 Key 没问题,问题就纯粹在预览渲染这一侧。

如果你打算长期用 Codex Desktop 做编码和 Agent 任务,可以考虑 Coding Plan,入口在 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite 。它适合那种每天都要跑代码生成、需要稳定额度的场景。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,配置细节以文档为准。

把模型这层理顺之后,我们回到预览本身。记住:预览裂图跟模型没关系,但 settings 文件是共用的,改的时候要精准定位到渲染相关字段,别误伤模型配置。

3. 可复制配置:settings 片段与 base href 注入

这一节给可直接复制的配置。Codex Desktop 的 settings 一般是一个 JSON 文件,路径因平台而异,macOS 常见在~/Library/Application Support/Codex/settings.json,Windows 在%APPDATA%\Codex\settings.json,Linux 在~/.config/Codex/settings.json。你要先确认自己机器上的真实路径,再往里加字段。

先给一份模型接入 + 预览渲染并存的 settings 片段,字段名以你本地实际版本为准,重点是结构:

{ "model": { "baseUrl": "https://taotoken.net/api", "apiKey": "sk-你的Key", "modelId": "你的ModelID" }, "markdown": { "preview": { "baseHrefMode": "fileDir", "allowLocalFileAccess": true, "resolveRelativeWithUrljoin": true, "encodePath": true } } }

baseHrefMode设成fileDir的意思是:渲染 HTML 时,把被预览.md文件所在目录作为<base href>写进<head>。这是解决相对路径裂图最关键的一步。allowLocalFileAccess放开 WebView 读本地文件的能力,但要注意安全边界,后面会讲。resolveRelativeWithUrljoin让相对路径用urljoin解析而不是字符串拼接,encodePath处理中文和空格。

如果你用的是 TOML 风格的配置(部分版本支持),等价写法:

[model] base_url = "https://taotoken.net/api" api_key = "sk-你的Key" model_id = "你的ModelID" [markdown.preview] base_href_mode = "fileDir" allow_local_file_access = true resolve_relative_with_urljoin = true encode_path = true

配置改完,Codex Desktop 需要重启预览进程才生效。有些版本是关掉预览面板再打开,有些要整个应用重启,实测下来重启应用最稳。

再给一段渲染层注入<base href>的参考实现,如果你在写自定义预览插件或调试渲染链路,这段能直接对照:

import os def render_markdown_html(md_path: str, html_body: str) -> str: dir_url = "file://" + os.path.abspath(os.path.dirname(md_path)) + "/" return f"""<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <base href="{dir_url}"> </head> <body> {html_body} </body> </html>""" if __name__ == "__main__": md = "/Users/me/doc/readme.md" body = '<img src="./images/arch.png">' print(render_markdown_html(md, body))

运行后你会看到<base href="file:///Users/me/doc/">,这样页面里所有相对路径都相对 md 目录解析,裂图消失。注意末尾那个斜杠不能少,少了会把最后一级目录当成文件名。

如果你更倾向在后端预解析相对路径,不依赖 base 标签,用urljoin把相对 src 算成绝对file://再注入:

import os from urllib.parse import urljoin def resolve_image_src(md_dir: str, src: str) -> str: if src.startswith(("http://", "https://", "data:")): return src if src.startswith("file://"): return src if os.path.isabs(src): return "file://" + src base = "file://" + md_dir + "/" return urljoin(base, src) if __name__ == "__main__": md_dir = "/Users/me/doc" print(resolve_image_src(md_dir, "./images/arch.png")) print(resolve_image_src(md_dir, "../assets/logo.png")) print(resolve_image_src(md_dir, "https://x.com/a.png"))

urljoin会自动处理./、../和多级回退,比手写字符串拼接可靠得多。网络图和data:内联图原样保留,不要试图把它们当本地文件解析。

注意:放开allowLocalFileAccess的同时,一定要用commonpath把读取范围锁死在 md 目录子树内,防止恶意 md 用../../etc/passwd逃逸到系统目录。安全边界不能省。

4. 验证请求:逐步确认图片真的加载成功

配置改完不能只看「好像显示了」,要逐步验证。第一步,把 md 里的图片改成绝对路径![x](/Users/me/doc/images/arch.png),如果预览能显示,说明预览器本身能读本地文件,问题就是相对解析。第二步,改回相对路径./images/arch.png,同时打开开发者工具看控制台。

Codex Desktop 的预览面板一般能通过快捷键或菜单打开 DevTools,看 Network 面板里图片请求的最终 URL。如果 URL 是http://localhost:port/images/arch.png,说明基址还是虚拟预览地址,baseHrefMode没生效。如果 URL 是file:///Users/me/doc/images/arch.png,说明基址对了,再看是不是被安全策略拦了。

第三步,检查控制台报错。Not allowed to load local resource是 WebView 拦截file://,需要allowLocalFileAccess。Failed to load resource: net::ERR_FILE_NOT_FOUND是路径算错了,多半是基址或编码问题。net::ERR_NAME_NOT_RESOLVED说明把本地路径当网络地址解析了,检查resolveRelativeWithUrljoin是否生效。

第四步,用一段最小 md 做隔离测试。新建test.md,内容只有一行![t](./images/arch.png),确保images/arch.png真实存在。这样排除掉多级目录、中文路径等干扰因素。如果最小用例能显示,再逐步加回复杂路径,定位是哪一级出的问题。

第五步,验证中文和空格路径。把图片放到我的 文档/images/架构 图.png,看预览是否正常。如果裂图,检查encodePath是否把空格编码成%20、中文是否做了 URL 编码。部分 WebView 对未编码的非 ASCII 路径不认。

第六步,验证../回退。把 md 放在doc/sub/readme.md,图片放在doc/images/arch.png,引用写成../images/arch.png。如果这个能显示,说明urljoin解析正确;如果裂图,说明还在用字符串拼接。

每一步都记录下最终 URL 和控制台输出,这样即使问题没一次解决,你也能明确知道卡在哪一环。实测下来,大部分裂图卡在第一步和第三步——要么基址没注入,要么 file 访问被拦。

5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth

改配置过程中会遇到几类典型报错,逐个对照。

401 Unauthorized:这是模型接入的报错,不是预览的。说明apiKey填错或过期,或者baseUrl写成了带路径的形式。检查 settings 里baseUrl是不是https://taotoken.net/api,Key 是不是从 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite 拿的那串。三件套 Base URL、Key、Model ID 要同时正确,缺一个都可能 401。

local proxy failed:这个报错通常出现在网络层,说明请求没走到目标端点。检查是不是本地有代理配置干扰,或者baseUrl写成了http而不是https。如果你在 settings 里同时配了模型和预览,确认改预览字段时没误删模型字段的引号或逗号,JSON 语法错误也会导致整个配置加载失败,表现成各种奇怪的连接问题。

reading choices相关报错:这是模型返回结构解析失败,常见于 Model ID 填错或端点返回了非预期格式。确认modelId跟你在模型对话页面测通的那个一致。如果模型对话页面正常、Codex Desktop 里报这个,多半是 settings 里的 modelId 拼写有出入。

OAuth相关报错:部分版本用 OAuth 流程接入,如果报 OAuth 失败,检查是不是同时配了 API Key 和 OAuth 两套凭证导致冲突。二选一,别混用。用 API Key 方式就清掉 OAuth 相关字段。

预览侧的报错再列一遍:Not allowed to load local resource对应allowLocalFileAccess没开;ERR_FILE_NOT_FOUND对应基址或编码错;图片显示但更新后不刷新,是 WebView 缓存,给图片 URL 加?v=<mtime>时间戳强制刷新。

如果你用 CC Switch 或 Cline MCP 这类工具管理配置,出现问题时同样要检查三件套 Base URL、Key、Model ID 是否完整。Codex 的auth.json里如果只填了 Key 没填 Base URL,请求会打到默认端点,表现成 401 或超时。三件套齐全是最低要求。

排查顺序建议:先确认模型接入正常(模型对话页面能出结果),再单独查预览。两件事混在一起查,容易互相干扰。

6. 语义一致 CTA:接入、验证、长期使用各走各的入口

预览修好之后,如果你还想把模型接入这条链路也理顺,按用途分流走对应入口,别只停在首页。

排障和接入配置相关,去 API Keys 页面拿 Key,再去接入文档对照字段: https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite 和 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 。文档里有各客户端的配置样例,Codex 的auth.json写法也在里面。

想先验证模型能不能正常出结果,用模型对话页面直接测: https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite 。输入一句话看返回,通了再往 Codex Desktop 里配。

长期做编码、跑 Agent 任务,需要稳定额度,看 Coding Plan: https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite 。控制台在 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite ,用量和额度都在那边看。

Claude Code 相关的接入配置,参考: https://taotoken.net/claude-code?utm_source=taotoken_aicg_blog_end&utm_content=claude_code&utm_campaign=rewrite 。

最后回到预览这件事本身。相对路径永远相对某个基址,预览器要渲染本地图,基址就必须是「被预览文件真实所在目录」,而不是「虚拟预览页地址」。这一条想通,裂图问题基本就解决了。剩下的都是细节:Windows 盘符file:///C:/...三个斜杠别写错,中文空格记得编码,网络图原样保留,安全边界用commonpath锁死。改完配置重启应用,打开 DevTools 看最终 URL,一步步验证,比反复猜要快得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询