Connect writes these for you, and Disconnect takes them out again. They're shown here so you know exactly what changes, or to set it up by hand.
Replies: the notify program
Codex runs its notify program after every turn and passes a JSON event as the last argument. The agent-turn-complete event carries last-assistant-message, and that's what gets read.
# ~/.codex/config.toml (at the very top)
notify = ["C:/Program Files/VoicedByMe/voicereader-say.exe", "--codex"]
Keep notify above every [table] header. In TOML, a key written below [mcp_servers] or [tui] belongs to that table, and Codex never sees it. That's the most common reason a notify program never runs.
Permission requests: a hook
// ~/.codex/hooks.json
{
"hooks": {
"PermissionRequest": [
{ "hooks": [{ "type": "command", "command": "…/voicereader-say --codex-hook", "timeout": 10 }] }
]
}
}
Speaking on request: an MCP server
[mcp_servers.voice-reader]
command = "C:/Program Files/VoicedByMe/voicereader-say.exe"
args = ["--mcp"]
The real entries also carry the local port and a private key, so only programs on your computer can make it talk.