My Codex Agents Were Posting to Slack as Me
On the morning of November 13 I found a message in a Slack thread with my AI team, posted under my name. It said a change I had agreed to had been applied, pasted about 3,900 characters of diff, and partway down carried
On the morning of November 13 I found a message in a Slack thread with my AI team, posted under my name. It said a change I had agreed to had been applied, pasted about 3,900 characters of diff, and partway down carried a heading that read "Text we are asking you to agree to (original)". At the bottom Slack had added the small footer it puts on messages sent through an app: "Sent using", then the app.
I hadn't written it. One of my agents had, and a minute later my orchestrator had replied to it.
The team is a set of LLM agents (two directors, executors, a reviewer and an orchestrator) that coordinate on a board of markdown files. My Slack messages reach them through a script called studio, which files each message in a team thread on the board as an item from human. Only the orchestrator has a reply command, slack reply.
The search
The Codex session log shows what happened. The agent was an executor running on Codex at maximum effort. Its task was step 6 of the studio's agreement procedure, which at the time said to "apply it and reply in its thread with the outcome and what .agents/bin/commitments printed." It didn't say who replies. Executors have no reply command.
So the executor went looking for one:
const matches = ALL_TOOLS.filter(x => /slack/i.test(x.name + " " + x.description));
The match came from codex_apps, the ChatGPT connectors attached to my account. It read the thread with slack_read_thread, assembled the message from the commitments output, wrote a branch to split it at a section heading if it ran past 4,800 characters, and sent it with slack_send_message.
Codex had told it to work this way. The system prompt in the same session log has a section on plugins and apps:
Filter
ALL_TOOLSby name or description to find relevant tools.
And under "Decision Boundary":
Discover and use relevant tools proactively when they may help carry out the requested task.
My procedure said reply. Codex said find a tool that helps.
What the studio did with my "message"
The studio picked up the post like any other message in the thread and filed it as from human. The orchestrator handled it from there.
It had happened before. Going back through the logs I found five posts between November 3 and 13, all from executors, all inside tasks that applied an agreed change. Two of them landed in the same thread 0.6 seconds apart on November 4, from two executors running in parallel. The first time, on November 3, the orchestrator noticed: "The new message was the text posted as the applied result, received again, so I didn't reply twice." By November 12 and 13 it no longer did. It answered as if I had written to it: "I've checked the report on applying the retrospective agreements."
The first contact was earlier still, and it only read. On November 2 an executor searched my Slack, public and private channels, for "the Slack thread where the human agreed" a set of criteria, then read the thread. The board already held what I had said there. The same section of the Codex prompt covers that too: "Consult those sources before asking the user for information they may already contain."
None of the posts approved anything. Each apply task followed a real "I agree" from me.
No check in the studio would have caught one that did. The team takes my words in a thread as mine. That same day I threw out a special !agree token that a Claude session had proposed for agreements: "Can't I just say I agree? Why does it need a special signal?" Agreeing in plain words works only if from: human means me, and through the first half of November it meant me or any Codex agent that searched its tools for "slack".
One agent had seen the footer. On November 12 a Claude session collecting facts for a retrospective wrote: "Observed, not judged: the apply task's executor 'Replied in the existing stakeholder thread'", and quoted the footer. Its job was to collect facts, and it did. I found the posts by reading Slack the next morning.
The fix
Agents on Codex start through a small wrapper, codex-isolate, which I wrote in September to turn off my user-level skills, plugins and MCP servers, so a run sees the project and nothing else. It is the same wrapper I changed for the truncation problem. It left apps alone. I hadn't known that was a separate switch.
codex features list showed apps stable true. The wrapper now turns the whole feature off:
# ChatGPT's connectors (Slack among them) act in the human's name; agents reach the human only through studio.
print("-c")
print("features.apps=false")
Step 6 no longer tells an unnamed someone to reply. The orchestrator answers every message in its thread, and nobody else does. On the Claude side, runs already start with --strict-mcp-config, and the claude.ai connectors didn't show up in them.
If you run Codex headless on an account with ChatGPT connectors, two checks:
codex features list | grep apps
grep -rl '"server":"codex_apps"' ~/.codex/sessions
The second lists sessions that actually called a connector. For November 6 to 13 it found exactly the two sessions that posted. A plain search for codex_apps matched 440, because the connectors appear in every tool list. Rollouts from earlier in the month were stored as .jsonl.zst, which grep won't read without zstdcat.
Slack was the only connector any agent called. It wasn't the only one they had. In all 440 of those sessions, the tool list also held Gmail, Google Drive, Calendar, Contacts and GitHub.
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.