Codex is the coding agent I use for local software tasks. ChatGPT is a separate application running in my normal signed-in Chrome profile.
I wanted a simple command such as “talk to GPT” to make Codex send a question to that ChatGPT session, wait for the answer, and bring both the reply and conversation link back.
Part 1 used direct browser control. I have deprecated that design.
The current design gives Codex one narrow local tool instead. Codex decides what to ask; my local agent owns how the browser interaction happens.
flowchart LR
C["Codex decides<br/>what to ask"]
A["Local agent runs<br/>the fixed workflow"]
G["My signed-in<br/>ChatGPT page"]
R["Answer and chat link<br/>return to Codex"]
C --> A --> G --> R
Figure 1. The current design in one view: Codex handles the question, while the local agent handles the repetitive browser procedure.
Contents
- 1. The request stays simple
- 2. What runs behind one request
- 3. Why I added a tiny startup adapter
- 4. Why each layer has one job
- 5. How one tool call becomes a browser task
- 6. How a later task finds the same conversation
- 7. The normal end-to-end test worked
- 8. The closed-Chrome test found a real bug
- 9. What is reliable now
- 10. Conclusion
1. The request stays simple
The prompt does not name a plugin, tool, server, or browser:
Talk to GPT and ask for a second opinion on this design.
The Chinese version is just as simple:
和 GPT 聊一下:请评价这个设计。
A Codex skill is a small instruction file that tells Codex when a workflow is appropriate and how to use it. This skill contains examples such as “ask ChatGPT,” “talk to GPT,” and “和 GPT 聊一下.” That lets Codex recognize an ordinary request without me naming the tool manually.
The skill tells Codex to call one operation: ask_chatgpt.
Codex discovers that operation through the Model Context Protocol, or MCP. MCP is an interface that lets another program expose callable tools to Codex. This project needs only one main tool, so Codex does not need to understand the browser procedure behind it.
2. What runs behind one request
The simple overview hides several smaller components:
flowchart LR
U["I say:<br/>talk to GPT"]
C["Codex"]
S["ChatGPT skill"]
M["Fast MCP adapter"]
A["WebContentAgent"]
Q["Private task record"]
X["Chrome Companion"]
G["Signed-in ChatGPT page"]
U --> C --> S --> M --> A --> Q --> X --> G
G -->|"Answer + conversation URL"| X --> A --> M --> C
Figure 2. The implementation path. Codex sees one stable tool; WebContentAgent and Chrome Companion perform the fixed browser workflow.
Here are the two less familiar names:
- WebContentAgent is my local program. It validates the request, records task state, starts Chrome when needed, and waits for the result.
- Chrome Companion is the browser-side component. It performs the actual actions on the visible ChatGPT page.
The private task record stays in an ignored runtime directory outside Git. It holds the exact request, current status, and eventual result so that a failure can be inspected instead of guessed at.
3. Why I added a tiny startup adapter
The first local-agent version connected the plugin directly to WebContentAgent’s Python MCP server. The server worked, but sometimes it was not ready when a new Codex task first asked, “What tools are available?” Codex could see the skill instructions, yet the ask_chatgpt tool had not appeared.
I fixed that startup race with a small Node adapter. It exposes ask_chatgpt immediately and starts the heavier Python path only when the tool is actually called.
New Codex task
↓
Node adapter exposes ask_chatgpt
↓
Codex calls the tool
↓
Python starts the WebContentAgent workflow
This keeps the installed plugin quick to discover while leaving validation, task state, and browser startup inside WebContentAgent.
4. Why each layer has one job
The design separates three responsibilities:
- Codex understands my request and decides what to ask ChatGPT.
- WebContentAgent validates the input, records task state, and owns the repeatable procedure.
- Chrome Companion interacts with the visible signed-in page.
This separation keeps product reasoning out of fragile page selectors and browser mechanics out of every Codex task. It also makes failures easier to name. A missing tool, an unclaimed task, an unsent prompt, and a missed completed response are different problems with different fixes.
5. How one tool call becomes a browser task
ask_chatgpt accepts a question and three optional controls:
question
optional attachment_path
optional conversation_url
timeout_seconds
WebContentAgent validates the destination and optional PDF, then creates the private task record. Its bridge listens only on 127.0.0.1, the computer’s loopback address, so the connection is available only on my own machine. The bridge also requires a companion token.
WebContentAgent starts ordinary Chrome at chatgpt.com. The companion runs inside my normal Chrome profile, where ChatGPT is already signed in. It does not copy cookies or export credentials.
For each task, the companion:
- opens the requested ChatGPT page;
- clears any synchronized draft;
- attaches the allowed PDF, if present;
- inserts the exact question;
- verifies the destination, filename, and prompt;
- submits once;
- waits for a completed assistant response;
- returns the response and conversation URL.
The exact-text check matters because ChatGPT can synchronize an unsent draft across tabs. Without clearing it first, a new question can be appended to old text.
The single-submit rule protects against the opposite problem. If the result is uncertain, the bridge inspects the existing task and conversation before another send. Blindly trying again could create a duplicate message.
6. How a later task finds the same conversation
A saved ChatGPT conversation has a URL such as:
https://chatgpt.com/c/<conversation-id>
The value after /c/ identifies one conversation. A later Codex task can reopen that full URL after the original task and local server have ended.
The skill stores a readable alias and the full URL in a small registry outside Git. It does not store prompts, responses, cookies, or tokens. A later request can use the alias to continue the same conversation without remembering the long URL.
7. The normal end-to-end test worked
After reinstalling the plugin and restarting Codex Desktop, I opened a completely new Codex task with this prompt:
和 GPT 聊一下:请让它随机生成一个新的 UUID,
并把 GPT 的完整回复和该 ChatGPT 对话链接带回来。
The task selected the skill automatically, called chatgpt_pdf_review.ask_chatgpt, and returned this UUID:
d7b3c2a1-8f64-4e9b-a5d2-1c7e90f43b68
It also returned the saved ChatGPT conversation URL. This verified one complete path from an ordinary request to automatic skill selection, local tool execution, ChatGPT submission, and an answer returned to Codex.
The normal case worked, but I also wanted to know whether the bridge could recover when Chrome was not already running.
8. The closed-Chrome test found a real bug
I closed Chrome, waited five minutes, and ran the same kind of request once.
WebContentAgent started Chrome automatically, and the companion claimed the task within one second. ChatGPT received the question and produced a valid UUID. However, the companion did not recognize the completed response, so the local bridge waited for 15 minutes and timed out.
A later read-only inspection found the finished answer in the ChatGPT conversation:
6808af0d-5f5e-47b4-b6e7-65fe25882e3e
That failure was narrower than it first appeared. Chrome startup worked. Task delivery worked. Submission worked. ChatGPT generated the answer. What failed was the final step: recognizing that generation had finished and returning the response.
The page had finished, but the companion never sent WebContentAgent the signal saying, “this task is complete.” Retrying the question would not fix the detector; it would only risk creating a duplicate conversation.
The review for this article exposed the same boundary again. The local agent submitted the review request and ChatGPT produced a full response, but the first detector result contained page citation titles instead of the review. A follow-up returned only the first heading. The complete answer was visible in the existing conversation, which confirmed that sending worked and response extraction remained the weak point.
9. What is reliable now
The current architecture is working, but it is not yet fully reliable from beginning to end. The known problem is detecting and extracting some completed ChatGPT responses.
The following parts have worked in live tests:
- natural-language skill discovery;
- fast MCP tool registration in a new Codex task;
- one validated local task record;
- automatic launch of the normal Chrome profile;
- exact prompt staging and one submission;
- saved ChatGPT conversation URLs;
- cross-task conversation aliases outside Git.
The response-return step still needs stronger completion and text detection. ChatGPT’s page can contain several representations of one assistant turn: streaming text, final text, citation cards, and headings. The element that looks correct while ChatGPT is generating is not always the one that contains the complete final answer.
Until that detector is fixed and retested, a timeout or suspiciously short result means “inspect the existing conversation,” not “send again.”
10. Conclusion
The local-agent design is now my current architecture. Codex sees one simple operation—ask ChatGPT—while WebContentAgent and Chrome Companion own the repetitive browser mechanics.
The system is not finished. The closed-Chrome test and this article’s GPT review both exposed the same response-detection problem: ChatGPT answered successfully, but the companion did not reliably return the complete response.
That failure also shows why the new architecture is useful. Instead of knowing only that “browser automation failed somewhere,” I can identify the broken boundary and improve that component without changing how Codex uses the workflow.
Comments