01
What it does
Codex and ChatGPT already have good feedback routes. The friction is the timing: the
moment worth reporting is usually the moment you are least willing to stop, and the
interactive /feedback route asks you to stop — open a dialog, fill it in,
decide about logs, submit.
This skill removes that trade-off for the private in-product route. When you tell your agent to file the bug, it writes a sanitized report, sends it through the same first-party upload request the desktop feedback dialog uses, and tells you what came back. Nothing opens. Nothing takes focus. Your task keeps running.
What it is
- A skill package for a Codex-style agent, plus one Node helper script.
-
A client of
feedback/uploadon the local Codex App Server that ships inside the ChatGPT/Codex desktop app and the Codex IDE extension. - An operating contract that keeps drafting, private sending, and public posting apart.
- Reviewable in one sitting: one Node script with no third-party imports, three shell scripts, no build step.
What it is not
- Not an OpenAI product, and not a documented public CLI subcommand.
- Not UI automation. It never clicks, types, screenshots, or scripts a menu.
- Not a log collector. Logs, browser data, screenshots and attachments stay off.
- Not a way to post publicly. GitHub issues need their own explicit permission.
02
The two paths
Both paths send the same first-party feedback/upload request — what happens
after the client accepts it is not something this project can observe, so it does not
claim it. They differ in what they cost you while you are working. Path A is the
documented interactive dialog, and it remains the honest fallback whenever the
background path is unavailable. Path B is what this skill adds. Step through it.
Path A interactive /feedback
your task, mid‑thought
Supported and documented. It also interrupts you, and it starts with diagnostics switched on.
Path B background feedback/upload
- report.txt
- readiness check
- local app server
- response
A short-lived local process, one JSON request over a pipe, and no window at all.
1 Draft, and send nothing
The agent writes the sanitized report to a private temporary file: what happened, what you expected, the build, the reproduction. Task ids, private paths, prompts and credentials get reviewed out. Drafting is not permission to send.
/tmp/report.txt ← sanitized text only
threadId ← the exact affected task
classification ← bug | bad-result | good-result | safety_check | other
2 Check compatibility, still sending nothing
--check asks your installed client to emit its own stable app-server
schema into a temporary directory, verifies that
feedback/upload and every payload field are still there, reports the
client it resolved, deletes the schema, and exits. It never opens a session and
never uploads. No test feedback is ever sent to prove the transport works.
$ node ~/.codex/skills/submit-chatgpt-feedback/scripts/submit-feedback.mjs --check
{"ok":true,"ready":true,"submitted":false,"ui":false,"method":"feedback/upload",
"surface":"codex_desktop","defaults":{"includeLogs":false,"extraLogFiles":null}}
3 Send exactly this, once authorized
The helper starts the bundled client's app server on a pipe, initializes, and
sends one request. Every diagnostic field is hard-coded off. Use
--dry-run first to print this payload without connecting.
{
"method": "feedback/upload",
"params": {
"classification": "bug",
"reason": "…your sanitized report…",
"threadId": "…the affected task…",
"includeLogs": false,
"extraLogFiles": null,
"tags": { "surface": "codex_desktop", "submission_source": "authorized_codex_agent" }
}
}
4 Accept only real evidence
Success is claimed only when the app server answers with a response that actually
carries threadId — which the desktop client presents as the
correlation identifier, and there is no separate support ticket number, so the
helper does not invent one. An error, a timeout, an early exit, or a reply that
merely lacks an error is reported as not submitted, and the interactive
/feedback handoff is offered rather than performed.
{"ok":true,"submitted":true,"method":"feedback/upload",
"threadId":"…","correlationIdentifier":"threadId",
"includeLogs":false,"extraLogFiles":null}
03
Install
On skill use, the agent checks for public updates at most weekly after a successful check, with a shared local lease. Available updates come with a brief explanation and a question: do you want to install this update? Installation happens only after you agree, through a safe route that preserves local edits. Python 3 is needed for the check; it starts no background service. You can opt out. Failed checks retry later; plugin updates use the host updater.
You need Node.js 18+ and a ChatGPT or Codex client installed locally. Nothing is downloaded at install time, nothing is compiled, and no packages are added. The installer copies the files listed in the package manifest and nothing else.
git clone https://github.com/EthanSK/submit-chatgpt-feedback.git
cd submit-chatgpt-feedback
./install.sh --dry-run # show exactly what would change
./install.sh # copy the manifest files into place
./check.sh # confirm what is installed
The default destination is
${CODEX_HOME:-~/.codex}/skills/submit-chatgpt-feedback. Pass
--dest <dir> to install somewhere else. Then verify that your local
client still exposes the upload route — this sends nothing:
node ~/.codex/skills/submit-chatgpt-feedback/scripts/submit-feedback.mjs --check
Removing it is symmetrical, and it will not delete a file you edited unless you insist:
./uninstall.sh --dry-run
./uninstall.sh # keeps files you modified
./uninstall.sh --force # removes them too
04
Use it
Most of the time you never touch the helper. You talk to your agent, and the skill tells the agent how to behave: find the affected task, sanitize, state the payload, send once, report the identifier verbatim.
“File this as a Codex bug from the task where it happened.”
“Draft feedback about this, but don't send it yet.”
“Report this and tell me exactly what you attached.”
To drive the helper yourself:
HELPER=~/.codex/skills/submit-chatgpt-feedback/scripts/submit-feedback.mjs
# 1. is the local route still there? (sends nothing)
node "$HELPER" --check
# 2. what exactly would be sent? (connects to nothing)
node "$HELPER" --report /tmp/report.txt --thread TASK_ID --dry-run
# 3. send it once
node "$HELPER" --report /tmp/report.txt --thread TASK_ID --classification bug
Use --no-thread for feedback that genuinely belongs to no task. On success
one line of JSON goes to standard output; on failure one line of JSON goes to standard
error and the exit status is 1. There is no partial success.
If two different first-party clients are running — the desktop app and the Codex VS Code extension, say — the surface cannot be inferred, so the helper refuses rather than guessing and lists what it found. Several processes of the same binary are deduplicated first, because that is not ambiguity. Name the client for the surface the report is about, and no UI is needed to do it:
node "$HELPER" --cli /path/to/ChatGPT.app/Contents/Resources/codex --check
That choice is load-bearing: surface, app_version and
app_build follow the binary you name, so a guess would mislabel an
otherwise correct report. An explicit --cli or
CODEX_FEEDBACK_CLI always wins and skips detection entirely.
06
Privacy and security
Everything runs locally against the Codex client you already trust with your work. This project has no server, no telemetry, no analytics, and no third-party code. The website you are reading loads no fonts, no scripts and no assets from anywhere else.
Sent, when you authorize a submission
- Your sanitized report text, exactly as reviewed.
- The classification and the affected
threadId, or none. - Client tags: app version and build, surface, and an honest submission source.
Never sent by this helper
- Logs —
includeLogsis hard-codedfalse. - Files —
extraLogFilesis hard-codednull. - Screenshots, browser tabs, browser logs, or session transcripts.
- Anything at all during
--checkor--dry-run.
Two extra guards refuse the send rather than trusting it: report text over the size
limit is rejected instead of truncated, so a pasted log cannot slip through, and text
matching an obvious credential shape — private key blocks, API-key and token
patterns, Authorization headers — is refused outright. Widening
diagnostics is deliberately not a flag on this helper. If you truly want to attach
logs, review them and call feedback/upload yourself with the exact
approved payload.
The full statement of what the code can and cannot do, the guards, the residual risks and how to report a vulnerability privately is in SECURITY.md.
07
Limitations, stated plainly
-
It rides an undocumented-for-external-use surface.
feedback/uploadis a real, stable, first-party request in the bundled app server's own generated schema, but public documentation describes/feedbackas a dialog and does not document this as an ordinary CLI subcommand. A client update can change or remove it. That is why--checkexists and why every failure is fail-closed. -
macOS is the verified platform. Client auto-detection knows about
/Applications,~/Applicationsand the VS Code Codex extension. Elsewhere it can still work with--cliorcodexonPATH, and it labels itselfplatformSupport: "unverified"rather than pretending. - No delivery guarantee beyond the response. A successful response means the upload was accepted. It is not a promise that a human will read it, and there is no status to poll.
- Sanitization is a judgement, not a filter. The credential guard is a backstop for obvious mistakes. Reviewing the report text is still the agent's and your responsibility.
- Public GitHub posting is out of scope for automation. The skill documents how to route and de-duplicate a public report; it does not post one for you without explicit permission.
-
The test suite never submits feedback. It exercises the whole protocol
against a local fake client, so passing tests do not prove your real client is
compatible.
--checkis how you prove that.
08
Architecture
The skill is instructions plus one script. SKILL.md is the operating
contract an agent reads; the references hold the conditional detail; the helper opens no
socket of its own — it exchanges JSON over ordinary pipes with a short-lived local
app-server child process, and the first-party client owns the network upload.
submit-chatgpt-feedback/
├─ skill/submit-chatgpt-feedback/ the distributable
│ ├─ SKILL.md operating contract and router
│ ├─ MANIFEST the only files install/uninstall touch
│ ├─ agents/openai.yaml display metadata
│ ├─ references/
│ │ ├─ background-upload.md the app-server route, honestly scoped
│ │ ├─ reporting-routes.md private vs public routing per surface
│ │ └─ evidence-quality.md observation vs inference, evidence bars
│ └─ scripts/submit-feedback.mjs the helper: check, dry-run, send
├─ install.sh · uninstall.sh · check.sh manifest-scoped lifecycle
├─ tests/ package, helper, installer, docs, privacy
├─ examples/ a worked report and real output shapes
└─ docs/ this site, dependency-free
The request path, end to end:
agent → report.txt (temp) → submit-feedback.mjs
│
├─ resolve the active bundled client
├─ spawn: codex app-server --stdio (local pipe)
├─ initialize
└─ feedback/upload ─────────────► the client's own upload path
◄───────────── { threadId }
Everything is verifiable from the repository:
read the helper,
read the contract, or run node tests/run-tests.mjs.