<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Iliya Oblakov</title>
    <description>The latest articles on DEV Community by Iliya Oblakov (@iliyaoblakov).</description>
    <link>https://dev.to/iliyaoblakov</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1578658%2F7b3b5d15-9e52-4626-9c3c-4f29f2e30440.jpg</url>
      <title>DEV Community: Iliya Oblakov</title>
      <link>https://dev.to/iliyaoblakov</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/iliyaoblakov"/>
    <language>en</language>
    <item>
      <title>My GEO for small business guide in Search Console: 215 impressions and an average position of 29.8</title>
      <dc:creator>Iliya Oblakov</dc:creator>
      <pubDate>Thu, 12 Nov 2026 11:54:07 +0000</pubDate>
      <link>https://dev.to/iliyaoblakov/my-geo-for-small-business-guide-in-search-console-215-impressions-and-an-average-position-of-298-gam</link>
      <guid>https://dev.to/iliyaoblakov/my-geo-for-small-business-guide-in-search-console-215-impressions-and-an-average-position-of-298-gam</guid>
      <description>&lt;p&gt;Google Search Console recorded 215 impressions, an average position of 29.8 and 0 clicks for the &lt;a href="https://rayastats.com/en/blog/geo-for-small-businesses" rel="noopener noreferrer"&gt;GEO for small business guide&lt;/a&gt; I published on 16 October 2026. That's the 28-day read from 12 November. These snapshots show search visibility for one guide. They don't establish how long GEO takes or whether AI engines recommend my business, and I haven't checked yet whether ChatGPT or any other AI engine names it.&lt;/p&gt;

&lt;p&gt;GEO is the work of getting a business found and recommended by AI engines. I practise it on rayastats.com, which I run alone, with modest traffic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Search Console numbers for the guide
&lt;/h2&gt;

&lt;p&gt;I read Google Search Console for the guide three times in November. Each read covers 28 days, so the windows overlap.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;6 November (7 October to 3 November): 93 impressions, 0 clicks, average position 41.6.&lt;/li&gt;
&lt;li&gt;10 November (28 days to 8 November): 185 impressions, 0 clicks, average position 31.8.&lt;/li&gt;
&lt;li&gt;12 November (last 28 days): 215 impressions, 0 clicks, average position 29.8.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The guide's average position was 29.8 in the latest read. These figures don't establish why it received no clicks.&lt;/p&gt;

&lt;p&gt;The first window starts on 7 October, nine days before the guide existed. Part of the rise from 93 to 185 is the later window covering more days the page was live.&lt;/p&gt;

&lt;p&gt;The query reports cover the whole site, with no breakdown by page. They recorded 41 and then 85 impressions for "geo for small business", and 21 and then 52 for "geo for small businesses". The 12 November read lists 97 and 66, but it doesn't record which query filter was applied. These records don't establish each query's share of the guide's impressions.&lt;/p&gt;

&lt;p&gt;Across the whole site, the 28 days read on 10 November show 447 impressions and 6 clicks. The 28 days before that show 39 impressions and 0 clicks. The guide accounts for none of those clicks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two long queries recorded average position 8
&lt;/h2&gt;

&lt;p&gt;In the 10 November read, the site recorded an average position of 8 for two long questions: "what should a small business look for in a geo platform?" (4 impressions) and "how to check if ai search bots can crawl and parse text on decision-stage pages?" (9 impressions). The short head term "geo for small business" recorded an average position of 34.3 in the same read.&lt;/p&gt;

&lt;p&gt;Those two queries totalled 13 impressions, too few to establish a ranking pattern.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I did on the site
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;16 October 2026: I published the GEO guide, about 2,200 words with a 10-step checklist.&lt;/li&gt;
&lt;li&gt;16 October 2026: I fixed a crawler problem I found while checking how crawlers see the site. English blog posts were also reachable under Bulgarian URLs and marked as Bulgarian, so search engines saw duplicate pages in the wrong language. Each post now has one address, and the other language redirects to it. Two posts also had two H1 headings, so their body headings became H2.&lt;/li&gt;
&lt;li&gt;19 October 2026: I published a second guide built on my own crawler data, &lt;a href="https://rayastats.com/en/blog/can-ai-engines-read-your-website" rel="noopener noreferrer"&gt;Can AI engines read your website?&lt;/a&gt; I linked it from the GEO guide and added a Guides section to the site's llms.txt.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The GEO guide's HTML snapshot from 12 November contains one structured data graph, including the BlogPosting, BreadcrumbList, OnlineBusiness, Person and Service types. The business is described as an online business with no street address, because I work fully online.&lt;/p&gt;

&lt;p&gt;I'm not claiming llms.txt or structured data moved any number in this post. The GEO guide itself says you don't need special files. I also can't tell you which of these changes, if any, moved impressions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Crawlers were visiting before any of this
&lt;/h2&gt;

&lt;p&gt;In the four weeks to 16 October 2026, my server logs showed 99 visits from OpenAI's search crawler (OAI-SearchBot), 62 from ClaudeBot (Anthropic's training crawler), 18 from PerplexityBot and 14 from Brave's crawler. Googlebot made 26 visits, verified against Google's published IP ranges. The AI crawler counts go by user agent only, so they're indicative.&lt;/p&gt;

&lt;p&gt;Those logs show crawlers fetching pages. They don't show what any engine says about the site.&lt;/p&gt;

&lt;h2&gt;
  
  
  What hasn't happened yet
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Clicks. The guide has 0 clicks from Google in every read. I'm not claiming a lead or a sale from it.&lt;/li&gt;
&lt;li&gt;An AI engine check of rayastats.com. I haven't tested whether ChatGPT or any other AI engine names my site. It's the next thing I'll run, and I'll publish it.&lt;/li&gt;
&lt;li&gt;Enough data. This is one small site and a few weeks of numbers, and Search Console impressions aren't visitors.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The check I'll run on my own business
&lt;/h2&gt;

&lt;p&gt;The method is the same 10 questions on 3 days in ChatGPT and Google AI Overviews. I tested it on one anonymous med spa in Dallas that isn't a client, from 23 to 25 October 2026. Nobody commissioned the sample. ChatGPT named the business in 3, 1 and 4 of 8 general answers on the three days. Google's AI Overview named it in 1 of 8 each day and gave the wrong Saturday hours all three days.&lt;/p&gt;

&lt;p&gt;A mention counts a name on the answer's map as well, and the 23 October ChatGPT count of 3 includes one map-only mention. One of the 8 general prompts left out Dallas and produced answers about Illinois in all three ChatGPT runs and in Google's first two runs. I kept it in the 8.&lt;/p&gt;

&lt;p&gt;A single day of ChatGPT would have given me 3, 1 or 4, depending on the day. There's a separate post on &lt;a href="https://rayastats.com/en/blog/check-if-chatgpt-names-your-local-business" rel="noopener noreferrer"&gt;how to check if ChatGPT names your local business&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'm doing next
&lt;/h2&gt;

&lt;p&gt;These are plans. None of them is done.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A new title for the guide, aimed at the exact searches in the query reports, and more links to it from related posts.&lt;/li&gt;
&lt;li&gt;A set of shorter posts around it, each built on one real check: wrong business facts in AI answers, the 3-day run, and what 50 med spa websites tell AI engines.&lt;/li&gt;
&lt;li&gt;The same 3-day question method on my own business. I'll publish the numbers.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  For local businesses in the US and UK
&lt;/h2&gt;

&lt;p&gt;The AI Visibility Audit shows whether ChatGPT and Google AI Overviews name and recommend your business, with screenshots, repeated tests and a ranked fix list. Every prompt runs 3 times in each engine, on 3 different days, with your city written into every prompt. The Full Audit takes 3 business days and the Audit + Fix Kit takes 5, counted from the day after your intake answers arrive. Business days exclude weekends and bank holidays in England and Wales. Nobody can guarantee a place in an AI answer, and the audit doesn't promise one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://rayastats.com/en/ai-visibility-audit" rel="noopener noreferrer"&gt;See what the AI Visibility Audit covers&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>seo</category>
      <category>ai</category>
      <category>smallbusiness</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Google AI Overview showed the wrong business hours for a Dallas med spa, 3 days out of 3</title>
      <dc:creator>Iliya Oblakov</dc:creator>
      <pubDate>Thu, 12 Nov 2026 11:53:39 +0000</pubDate>
      <link>https://dev.to/iliyaoblakov/google-ai-overview-showed-the-wrong-business-hours-for-a-dallas-med-spa-3-days-out-of-3-1keo</link>
      <guid>https://dev.to/iliyaoblakov/google-ai-overview-showed-the-wrong-business-hours-for-a-dallas-med-spa-3-days-out-of-3-1keo</guid>
      <description>&lt;p&gt;If Google's AI Overview shows the wrong hours for your business, ask it the same question on 3 different days and compare the answer with every place your hours are published. A listing that repeats the same disputed hours is worth checking, but the match doesn't show where the engine got them. I ran that test on a Dallas med spa. The Overview included a Saturday 11:00 AM to 4:00 PM window in all three runs, and a third-party directory listing showed the same window. The med spa's homepage listed Saturday as by appointment only. The first fix I recommended was to make the Saturday hours identical everywhere they appear, then ask that directory to correct its listing.&lt;/p&gt;

&lt;p&gt;This is one narrow case of &lt;a href="https://rayastats.com/en/blog/geo-for-small-businesses" rel="noopener noreferrer"&gt;GEO for small businesses&lt;/a&gt;: an engine names you and gets a fact wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I tested
&lt;/h2&gt;

&lt;p&gt;The sample audit covered one real med spa in Dallas, Texas. It isn't a client and nobody commissioned the audit, so it stays anonymous.&lt;/p&gt;

&lt;p&gt;I asked the same 10 questions on Friday 23, Saturday 24 and Sunday 25 October 2026, typed the same way each day, in a new chat or search, with no follow-up questions. Eight were general questions a new client might ask. Two asked about the med spa by name: what it's known for, and its address, phone number and opening hours.&lt;/p&gt;

&lt;p&gt;ChatGPT and Google AI Overviews ran in signed-out browser sessions and answered on all 3 days. Claude ran once, on 25 October, through a signed-in Claude Code account in command line mode with web search on, from a PC in Bulgaria. That setup may differ from the Claude app. Perplexity and Gemini couldn't be used signed out, so they have no results.&lt;/p&gt;

&lt;p&gt;I checked the facts in the by-name answers against the med spa's own website. I didn't open the Google Business Profile itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  What each engine got right by name
&lt;/h2&gt;

&lt;p&gt;All three engines that returned answers named the med spa on every by-name question.&lt;/p&gt;

&lt;p&gt;These scores cover the facts I checked against the business website, mostly using the 23 October check as the baseline. Details I couldn't verify were left out, so a passing answer wasn't necessarily verified in full.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;ChatGPT: 6 of 6 answers matched the website on the facts checked.&lt;/li&gt;
&lt;li&gt;Google AI Overviews: 3 of 6. "Known for" matched on 3 of 3 runs. For address, phone and hours, 0 of 3 answers fully matched the website, because Saturday differed.&lt;/li&gt;
&lt;li&gt;Claude: 2 of 2 on its single run, counting the facts that could be checked.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The AI Overview's wrong hours were on Saturday
&lt;/h2&gt;

&lt;p&gt;Google's AI Overview included a Saturday window of 11:00 AM to 4:00 PM in all three runs. The answer from the 24 October run also said "or by appointment only". The address, the phone number and the weekday hours matched the website. The homepage baseline listed Saturday as by appointment only.&lt;/p&gt;

&lt;p&gt;The same 11:00 AM to 4:00 PM window appeared on a third-party directory listing. That match doesn't show where the Overview got it. On Friday, the Overview's source card for the hours answer was the Google business listing plus an inline link to the business website. Google's search snippet for the med spa's own contact page showed a third Saturday time. So the business's own web presence wasn't consistent either.&lt;/p&gt;

&lt;p&gt;ChatGPT gave Saturday as by appointment only in all three runs. Friday's answer cited the contact page. My notes for the Saturday and Sunday runs record links to the business website. Claude's single answer said "by appointment only" and pointed out, without being asked, that two directory listings say Saturday 11 to 4.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the site's own code said
&lt;/h2&gt;

&lt;p&gt;Structured data is a block of code that hands search engines and AI tools the business name, address, phone and hours in a fixed format. On 22 October 2026 the med spa's homepage had this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Type: LocalBusiness.&lt;/li&gt;
&lt;li&gt;Business name: misspelled, with the words run together.&lt;/li&gt;
&lt;li&gt;Address: street, city, state and ZIP present, suite number missing.&lt;/li&gt;
&lt;li&gt;Phone: missing.&lt;/li&gt;
&lt;li&gt;Opening hours: missing.&lt;/li&gt;
&lt;li&gt;A second block, WebSite: name shortened to one word.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The 22 October robots.txt check found a sitemap declaration and no explicit block for the AI crawlers the sample looked at. One unrelated search crawler was blocked.&lt;/p&gt;

&lt;p&gt;One place the engine could have read the right hours was empty. This sample didn't prove that the gap caused the wrong Overview answer. A code-only check of 50 Texas med spa homepages is in &lt;a href="https://rayastats.com/en/blog/med-spa-websites-what-ai-engines-read" rel="noopener noreferrer"&gt;What 50 med spa websites tell AI engines about themselves&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Directory pages showed up as sources
&lt;/h2&gt;

&lt;p&gt;In three of Claude's linked answers, the links went to directory or booking pages about the med spa instead of its own website. For "best med spas in Dallas", ChatGPT's sources on Friday were directory sites and Google.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix list from the report
&lt;/h2&gt;

&lt;p&gt;Seven recommendations from the sample report, in the report's order. The last one is reworded as a check, because the team page already named both co-owners.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Owner: make the Saturday hours the same on the homepage, the contact page, the Google Business Profile and the directory listings. Then ask the directory showing 11:00 AM to 4:00 PM to correct it.&lt;/li&gt;
&lt;li&gt;Developer: correct the business name in the LocalBusiness structured data so it matches the sign and the Google profile letter for letter.&lt;/li&gt;
&lt;li&gt;Developer: add the phone number, the opening hours (matching fix 1) and the suite number to the LocalBusiness structured data.&lt;/li&gt;
&lt;li&gt;Developer: change the WebSite structured data name from the one-word short form to the full business name.&lt;/li&gt;
&lt;li&gt;Owner: check the listing on each directory ChatGPT used as a source for "best med spas in Dallas", and add or correct it.&lt;/li&gt;
&lt;li&gt;Owner: check how Google's Overview describes the ownership. I couldn't find that description on the pages I checked. If it's right, say it on the team page. If it's wrong, check the cited pages and other public listings, and correct any inaccurate ownership description you find.&lt;/li&gt;
&lt;li&gt;Content: check whether both co-owners' names and roles are on the site as text a crawler can read. ChatGPT described only one of them as a co-owner.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I didn't apply these recommendations or run an after-fix check, so I can't report whether the Overview changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to check AI answers about your own business
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Ask each engine, logged out, in a new chat or search: "What is the address, phone number and opening hours of [business] in [city]?" and "What is [business] in [city] known for?"&lt;/li&gt;
&lt;li&gt;Do it on 3 different days. Write down every fact and the source the answer shows.&lt;/li&gt;
&lt;li&gt;Compare what you wrote down with your website, your Google Business Profile and your directory listings. Mark every value that conflicts, next to the sources the answer cited.&lt;/li&gt;
&lt;li&gt;Check that your structured data carries the same name, address, phone and hours. Google's Rich Results Test or the page's source shows the LocalBusiness block.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The post on &lt;a href="https://rayastats.com/en/blog/check-if-chatgpt-names-your-local-business" rel="noopener noreferrer"&gt;how to check whether ChatGPT and Google AI Overviews name your local business&lt;/a&gt; has a weekly routine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits
&lt;/h2&gt;

&lt;p&gt;One med spa, one city, three days, with one Claude run and no Perplexity or Gemini results. The sample doesn't show why an engine picked a source. "Wrong" means the answer didn't match the website baseline, which mostly came from the 23 October check. Details I couldn't verify aren't in the scores. Google didn't consistently show Dallas as its location: two Saturday searches showed a town in Bulgaria, and another showed Illinois.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI Visibility Audit
&lt;/h2&gt;

&lt;p&gt;If you run a local business in the US or UK, the &lt;a href="https://rayastats.com/en/ai-visibility-audit" rel="noopener noreferrer"&gt;AI Visibility Audit&lt;/a&gt; shows whether ChatGPT and Google AI Overviews name and recommend it, with screenshots, repeated tests and a ranked fix list. It runs every prompt 3 times in each engine, on 3 different days, with your city written into every prompt. The Full Audit includes a prioritized fix list split into owner, developer and content tasks. Nobody can guarantee a place in an AI answer, and the audit doesn't promise one. The Full Audit takes 3 business days and the Audit + Fix Kit takes 5, counted from the day after your intake answers arrive. Business days exclude weekends and bank holidays in England and Wales.&lt;/p&gt;

</description>
      <category>seo</category>
      <category>ai</category>
      <category>smallbusiness</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I rechecked 23 public MCP issues on GitHub. OAuth sign-in and docs mismatches came up most.</title>
      <dc:creator>Iliya Oblakov</dc:creator>
      <pubDate>Wed, 11 Nov 2026 06:58:18 +0000</pubDate>
      <link>https://dev.to/iliyaoblakov/i-rechecked-23-public-mcp-issues-on-github-oauth-sign-in-and-docs-mismatches-came-up-most-46n6</link>
      <guid>https://dev.to/iliyaoblakov/i-rechecked-23-public-mcp-issues-on-github-oauth-sign-in-and-docs-mismatches-came-up-most-46n6</guid>
      <description>&lt;p&gt;On 10 November 2026 I went through public GitHub issues on MCP servers that were already in that week's research notes, and rechecked each issue the same day through the public GitHub Issues API. The set is 23 issues, and two classes came up more than any other: OAuth sign-in that fails before the server is usable, and docs that don't match the live server.&lt;/p&gt;

&lt;p&gt;At the recheck, about 16:00 Europe/Sofia, 21 issues were open and 2 had been closed as completed earlier that day. 5 of the 23 are feature requests, and I list them separately near the end. Some of the open issues were opened in October.&lt;/p&gt;

&lt;p&gt;I didn't file these issues and I didn't reproduce them, so everything below is what the issue itself says. I don't name the projects. Each section gives the failure class, then what to check on your own connector.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. OAuth sign-in fails before the server is usable
&lt;/h2&gt;

&lt;p&gt;Five open issues describe a client that can't finish sign-in, or that signs in and then gets an expired-session or invalid-client error.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Dynamic client registration accepts a loopback redirect URI such as &lt;code&gt;http://127.0.0.1:53682/&lt;/code&gt;, then the authorize endpoint answers &lt;code&gt;invalid_redirect&lt;/code&gt; for that same URI. Repeating the flow with &lt;code&gt;http://localhost:53682/&lt;/code&gt; works, and registering with &lt;code&gt;application_type: "native"&lt;/code&gt; doesn't change the result.&lt;/li&gt;
&lt;li&gt;A hosted server can't be connected from the Claude.ai connector because OAuth fails at dynamic client registration. The quoted error begins "Couldn't register with" and then names the vendor's sign-in service.&lt;/li&gt;
&lt;li&gt;A second server fails the same way when it's added as a remote connector in Claude Desktop with automatic registration. This issue was opened in October and was still open on 10 November.&lt;/li&gt;
&lt;li&gt;VS Code's MCP client registers through &lt;code&gt;POST /register&lt;/code&gt; and persists the &lt;code&gt;client_id&lt;/code&gt; it's issued. After the server evicts or expires that id, &lt;code&gt;/authorize&lt;/code&gt; fails with "Invalid client_id".&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;initialize&lt;/code&gt; succeeds, then every &lt;code&gt;tools/call&lt;/code&gt; returns a session-expired message or a 401 for a dynamically registered client. The issue says a fresh authorization and a brand-new client registration still fail within two minutes of the token being issued, while the same account works at the same time through the Claude connector.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Check on your connector:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Register a client dynamically, then send the redirect URI that registration accepted to the authorize endpoint. Do it once with &lt;code&gt;127.0.0.1&lt;/code&gt; and once with &lt;code&gt;localhost&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Add the server as a connector in each client your users run, with automatic registration, and watch whether registration completes.&lt;/li&gt;
&lt;li&gt;Remove a registered &lt;code&gt;client_id&lt;/code&gt; on the server, start sign-in again from the client that still holds it, and read the error the client gets.&lt;/li&gt;
&lt;li&gt;After &lt;code&gt;initialize&lt;/code&gt;, call a tool with a token issued a few seconds earlier to a dynamically registered client.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  2. Docs, skills or the listed schema don't match the live server
&lt;/h2&gt;

&lt;p&gt;Five issues fall here. Two of them were closed as completed on 10 November, so I describe those two in the past tense.&lt;/p&gt;

&lt;p&gt;Closed as completed:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A documented Claude Desktop setup put a remote server in &lt;code&gt;claude_desktop_config.json&lt;/code&gt; with only &lt;code&gt;url&lt;/code&gt; and &lt;code&gt;headers&lt;/code&gt;. The issue says that file only accepts local stdio servers, where each entry needs a &lt;code&gt;command&lt;/code&gt;, so Claude Desktop rejected the entry and the server never loaded. The docs also said 6 or 7 tools depending on the page, while the server registers 8 core tools plus 6 more on API-key connections. The issue was closed 25 minutes after it was opened. A comment on it says the production docs were updated and verified live the same day: the Claude Desktop pages now give an mcp-remote &lt;code&gt;command&lt;/code&gt; entry, and the API reference says 8 core tools.&lt;/li&gt;
&lt;li&gt;A config class had a fluent &lt;code&gt;instructions()&lt;/code&gt; method, but &lt;code&gt;filters&lt;/code&gt; was only a public property, so &lt;code&gt;-&amp;gt;filters([])&lt;/code&gt; threw an unknown-method error. The issue was closed as completed the same day.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Open on 10 November:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A hosted &lt;code&gt;tools/list&lt;/code&gt; marks a parameter as required on two tools. The issue says calls without it succeed, sending it doesn't change the output, and the &lt;code&gt;main&lt;/code&gt; branch has no such parameter on either tool.&lt;/li&gt;
&lt;li&gt;A README's Claude Code install command uses &lt;code&gt;npx -y @anthropic-ai/mcp-remote&lt;/code&gt;. The issue says that npm package doesn't exist and the registry returns 404.&lt;/li&gt;
&lt;li&gt;Two skills call MCP actions that the live tool reference doesn't list. The reporter checked the public tool reference on 9 November 2026.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Check on your connector:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run every documented install command, and paste every documented config block into the client it names, on a machine that has never had your server on it.&lt;/li&gt;
&lt;li&gt;Count the tools &lt;code&gt;tools/list&lt;/code&gt; returns, then compare that number with every page that states one.&lt;/li&gt;
&lt;li&gt;Diff the hosted &lt;code&gt;tools/list&lt;/code&gt; against the tool definitions on your main branch, required parameters included.&lt;/li&gt;
&lt;li&gt;For each skill you ship, look up every action it calls in the live tool list.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  3. Tool annotations a directory checklist flags
&lt;/h2&gt;

&lt;p&gt;One issue, opened in October and still open on 10 November, says none of a server's 39 registered tools passes a &lt;code&gt;title&lt;/code&gt;. It states that Anthropic's Connectors Directory requires a &lt;code&gt;title&lt;/code&gt; plus &lt;code&gt;readOnlyHint&lt;/code&gt; or &lt;code&gt;destructiveHint&lt;/code&gt; on every tool. It also says two tools that overwrite an existing object at the same key are marked &lt;code&gt;destructiveHint: false&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Check on your connector:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Confirm every tool has a &lt;code&gt;title&lt;/code&gt; and carries &lt;code&gt;readOnlyHint&lt;/code&gt; or &lt;code&gt;destructiveHint&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Call each write tool against a target that already exists. If the call replaces what was there, the tool modifies data and &lt;code&gt;destructiveHint: false&lt;/code&gt; is the wrong annotation.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  4. Install, runtime and argument coercion
&lt;/h2&gt;

&lt;p&gt;Three open issues fall here.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A fresh install exits on startup with &lt;code&gt;ImportError: cannot import name 'eval_type_backport' from 'pydantic._internal._typing_extra'&lt;/code&gt;. The issue attributes it to a pin on &lt;code&gt;mcp[cli]==1.6.0&lt;/code&gt; with pydantic left unpinned, after pydantic 2.14.0 removed that private name.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Number(null)&lt;/code&gt; and &lt;code&gt;Number("")&lt;/code&gt; are both 0, so an optional max-age parameter sent as &lt;code&gt;null&lt;/code&gt; becomes 0, and 0 forces a live crawl.&lt;/li&gt;
&lt;li&gt;A tool call ended with &lt;code&gt;MCP call failed: TimeoutError: MCP call timed out after 300.0s (configured timeout: 300.0s)&lt;/code&gt; and no result. The issue records one attempt, so it says nothing about how often this happens or why.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Check on your connector:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install into an empty environment, with no lockfile and no cached image, and start the server.&lt;/li&gt;
&lt;li&gt;Send &lt;code&gt;null&lt;/code&gt; and &lt;code&gt;""&lt;/code&gt; for every optional number, and compare the result with leaving the field out.&lt;/li&gt;
&lt;li&gt;Make the upstream API hang, then look at what the client receives and how long it waits.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  5. The connection reports connected and the agent has zero tools
&lt;/h2&gt;

&lt;p&gt;One open issue reports a regression after a version upgrade: &lt;code&gt;tools/list&lt;/code&gt; returns 403 and the agent sees zero tools, while the connection is still reported as connected.&lt;/p&gt;

&lt;p&gt;Check on your connector: after every upgrade, call &lt;code&gt;tools/list&lt;/code&gt; with the credentials an agent uses and count the tools that come back. The connected status in that report didn't show the failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. A cache keyed on the raw caller URL, with no size bound
&lt;/h2&gt;

&lt;p&gt;One issue, opened in October and still open on 10 November, says a docs client keys its cache on the caller's raw URL, has no size bound and no eviction, and removes an expired entry only when the same key is read again. It adds that the live docs site returns 200 for query-variant URLs, so each variant is cacheable.&lt;/p&gt;

&lt;p&gt;Check on your connector: for every cache in the server, write down the key, the size bound and the moment an expired entry is removed. If the key is caller input, count how many distinct keys one caller can create.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. The documented remote URL doesn't accept the connection
&lt;/h2&gt;

&lt;p&gt;One issue, opened in October and still open on 10 November, says adding the documented remote MCP URL as a Claude.ai custom connector fails at the first step with "Couldn't reach this address". After "Continue anyway" the connector saves, but it stays in a "needs reconnect" state with no tools exposed.&lt;/p&gt;

&lt;p&gt;Check on your connector: add the URL from your docs as a custom connector in the client the docs name, and confirm the tools appear.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Link-formatted values that don't open
&lt;/h2&gt;

&lt;p&gt;One open issue says link-formatted values in an MCP view don't open in Claude on the web, and that the view should use &lt;code&gt;ui/open-link&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Check on your connector: click every link your views render, in the web client.&lt;/p&gt;

&lt;h2&gt;
  
  
  The five feature requests
&lt;/h2&gt;

&lt;p&gt;Five of the 23 issues ask for something new, and I don't count them as failures:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;OAuth support in an MCP client toward MCP servers (RFC 9728 discovery and PKCE).&lt;/li&gt;
&lt;li&gt;Per-user SQL identity through JWT passthrough on a shared HTTP deployment.&lt;/li&gt;
&lt;li&gt;MCP management of public sharing.&lt;/li&gt;
&lt;li&gt;Create-environment and delete-variables tools on a hosted MCP server.&lt;/li&gt;
&lt;li&gt;Native Streamable HTTP and/or SSE transport, so a server can be hosted remotely instead of only as local stdio. This request was opened in October and was still open on 10 November.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  MCP connector review
&lt;/h2&gt;

&lt;p&gt;If you want an outside review before your connector goes to customers or the Claude directory, I do an MCP connector review: an independent review of your existing MCP connector against Anthropic's connector pre-submission checklist.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Every tool goes through the MCP Inspector, and through Claude as a custom connector where hosting allows it.&lt;/li&gt;
&lt;li&gt;Every tool is tested with valid input, bad input, denied access and an upstream error.&lt;/li&gt;
&lt;li&gt;The permission boundary is checked with two users.&lt;/li&gt;
&lt;li&gt;You get a checklist report where each finding has severity, impact, reproduction steps and a proposed fix.&lt;/li&gt;
&lt;li&gt;You get one retest after you fix the findings.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I don't claim the review would have caught every class in this article. Anthropic doesn't run, approve or certify it, and it can't guarantee a Community or Verified listing.&lt;/p&gt;

&lt;p&gt;The scope is on the &lt;a href="https://rayastats.com/en/ai-connector-review" rel="noopener noreferrer"&gt;MCP connector review&lt;/a&gt; page.&lt;/p&gt;

&lt;p&gt;If the server isn't built yet, the &lt;a href="https://rayastats.com/en/ai-connector-build" rel="noopener noreferrer"&gt;MCP build&lt;/a&gt; is an MCP server over your existing API: four to six read-only tools, one sign-in flow and deployment to your infrastructure. Following the checklist doesn't mean Anthropic approves or certifies the connector.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>oauth</category>
      <category>testing</category>
    </item>
    <item>
      <title>Read-only Codex starts your MCP servers too. Disable them before the review.</title>
      <dc:creator>Iliya Oblakov</dc:creator>
      <pubDate>Wed, 11 Nov 2026 06:57:40 +0000</pubDate>
      <link>https://dev.to/iliyaoblakov/read-only-codex-starts-your-mcp-servers-too-disable-them-before-the-review-5932</link>
      <guid>https://dev.to/iliyaoblakov/read-only-codex-starts-your-mcp-servers-too-disable-them-before-the-review-5932</guid>
      <description>&lt;p&gt;A &lt;code&gt;codex exec -s read-only&lt;/code&gt; review can't write files, and the MCP servers in your &lt;code&gt;~/.codex/config.toml&lt;/code&gt; start for it anyway. A server that can change things outside the sandbox works against a read-only review, so the Codex notes in my review gate say to set &lt;code&gt;enabled = false&lt;/code&gt; on that server while the review runs.&lt;/p&gt;

&lt;p&gt;Hooks come along too. A Stop hook that asks Codex to continue after its review can replace the final message with an empty one, and an empty final message leaves the gate with no verdict to save.&lt;/p&gt;

&lt;p&gt;I listed both in my &lt;a href="https://rayastats.com/en/blog/use-claude-code-and-codex-together" rel="noopener noreferrer"&gt;setup lessons post&lt;/a&gt;. This post follows one review through the gate and puts each fix next to what the official Codex docs say.&lt;/p&gt;

&lt;h2&gt;
  
  
  One review, in order
&lt;/h2&gt;

&lt;p&gt;My gate calls Codex like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;codex &lt;span class="nb"&gt;exec&lt;/span&gt; -s read-only --color never --output-last-message &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;file&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Around that call, in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;review.sh&lt;/code&gt; deletes any verdict already saved for the commit, so a failed rerun can't leave an old ACCEPT behind.&lt;/li&gt;
&lt;li&gt;It sends &lt;code&gt;review-prompt.md&lt;/code&gt;, the commits and the full diff from the base to HEAD to Codex on stdin.&lt;/li&gt;
&lt;li&gt;Without &lt;code&gt;REVIEW_MODEL&lt;/code&gt;, Codex takes the model and reasoning effort from &lt;code&gt;~/.codex/config.toml&lt;/code&gt;, and the MCP servers configured there start too.&lt;/li&gt;
&lt;li&gt;The sandbox lets Codex read files and run read-only commands such as &lt;code&gt;git diff&lt;/code&gt;. It can't write.&lt;/li&gt;
&lt;li&gt;Codex writes the review. Hooks set up for Codex run under &lt;code&gt;codex exec&lt;/code&gt; as well, a Stop hook included.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--output-last-message&lt;/code&gt; saves the final message to a file, and &lt;code&gt;review.sh&lt;/code&gt; reads the verdict from that file only.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;merge-check.sh&lt;/code&gt; refuses any commit that has no saved verdict.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The official non-interactive docs (checked 10 Nov 2026) cover the flags: &lt;code&gt;codex exec&lt;/code&gt; runs in a read-only sandbox by default, &lt;code&gt;--sandbox workspace-write&lt;/code&gt; allows edits, &lt;code&gt;-o&lt;/code&gt; is the short form of &lt;code&gt;--output-last-message&lt;/code&gt;, and &lt;code&gt;codex exec -&lt;/code&gt; takes the prompt from stdin. The two leaks come in at steps 3 and 5.&lt;/p&gt;

&lt;h2&gt;
  
  
  MCP servers from config.toml start for the review
&lt;/h2&gt;

&lt;p&gt;The official MCP page says Codex keeps MCP configuration in &lt;code&gt;config.toml&lt;/code&gt;, by default &lt;code&gt;~/.codex/config.toml&lt;/code&gt;, and that a trusted project can scope its own servers in &lt;code&gt;.codex/config.toml&lt;/code&gt;. Before a review, go through both files for servers that can change things outside the sandbox and switch each one off:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="k"&gt;[mcp_servers.&amp;lt;name&amp;gt;]&lt;/span&gt;
&lt;span class="na"&gt;enabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The page describes &lt;code&gt;enabled&lt;/code&gt; as optional: "Set &lt;code&gt;false&lt;/code&gt; to disable a server without deleting it." The entry stays in the file, so switching the server back on after the review is a one-line change.&lt;/p&gt;

&lt;p&gt;The official page documents the switch. It doesn't say, on its own, that a read-only &lt;code&gt;codex exec&lt;/code&gt; starts MCP servers. That behaviour comes from the Codex notes in my gate, the same notes the setup lessons post draws on.&lt;/p&gt;

&lt;p&gt;Three more settings from the same docs affect a review:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;required = true&lt;/code&gt; makes startup fail "if this enabled server isn't ready". The non-interactive page adds that when such a server fails to initialize, &lt;code&gt;codex exec&lt;/code&gt; exits with an error instead of continuing without it. My gate treats any reviewer exit other than 0 as &lt;code&gt;REVIEW FAILED&lt;/code&gt; and saves no verdict, so a required server that won't start costs you the review.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--ignore-user-config&lt;/code&gt; is for "a run that doesn't load &lt;code&gt;$CODEX_HOME/config.toml&lt;/code&gt;". My gate's call doesn't pass it, and the gate takes the model and reasoning effort from &lt;code&gt;~/.codex/config.toml&lt;/code&gt; unless &lt;code&gt;REVIEW_MODEL&lt;/code&gt; is set. The docs describe the flag in terms of that one file, and project-scoped servers sit in the repo's &lt;code&gt;.codex/config.toml&lt;/code&gt;, so check the repo under review as well.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;enabled_tools&lt;/code&gt; and &lt;code&gt;disabled_tools&lt;/code&gt; are tool allow and deny lists for a single server. The fix in my gate notes turns off the whole server.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To see which MCP tools a Codex run calls, try the review prompt once with &lt;code&gt;codex exec --json&lt;/code&gt; outside the gate. Stdout becomes a JSON Lines stream of the events Codex emits, and the documented item types include MCP tool calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Stop hook can empty the final message
&lt;/h2&gt;

&lt;p&gt;Hooks set up for Codex also run under &lt;code&gt;codex exec&lt;/code&gt;. A Stop hook that asks Codex to continue after its review can replace the final message with an empty one.&lt;/p&gt;

&lt;p&gt;My gate reads the verdict from the final message and never searches the log. The prompt Codex gets contains &lt;code&gt;Verdict: ACCEPT&lt;/code&gt; as the example of the format, so a search of anything that repeats the prompt can find an ACCEPT the reviewer never gave. An empty final message leaves nothing to read.&lt;/p&gt;

&lt;p&gt;On 23 October I ran the no-verdict cases in a scratch repo with my gate's scripts. The empty answer came from a small fake &lt;code&gt;codex&lt;/code&gt; I wrote for the test, and &lt;code&gt;review.sh&lt;/code&gt; printed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NO VERDICT: codex wrote no final message
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It exited 1 and saved no verdict. A Stop hook ends the review on the same line, with the diagnostic log kept and nothing saved. &lt;code&gt;merge-check.sh&lt;/code&gt; then stops with "no review verdict for ; run review.sh", so the commit can't pass and the review you waited for says nothing.&lt;/p&gt;

&lt;p&gt;The gate notes give two ways out:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Switch the hook off for reviews.&lt;/li&gt;
&lt;li&gt;Add a line to &lt;code&gt;review-prompt.md&lt;/code&gt; asking Codex to repeat its complete review if anything asks it to continue.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;a href="https://rayastats.com/en/blog/review-gate-no-verdict-crash" rel="noopener noreferrer"&gt;no-verdict post&lt;/a&gt; covers the other ways a review ends without a verdict: a crash, a cut-off answer and two Verdict lines.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Claude Code is the reviewer
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;BUILDER=codex&lt;/code&gt; swaps the roles, and my gate calls Claude Code like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude -p &lt;span class="s2"&gt;"Do the review described on standard input."&lt;/span&gt; --restricted --strict-mcp-config \
  --permission-mode dontAsk --tools &lt;span class="s2"&gt;"Read,Grep,Glob"&lt;/span&gt; --allowedTools &lt;span class="s2"&gt;"Read,Grep,Glob"&lt;/span&gt; \
  --disallowedTools &lt;span class="s2"&gt;"Bash,Edit,MultiEdit,Write,NotebookEdit,WebFetch,WebSearch,Task,Agent"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--strict-mcp-config&lt;/code&gt; with no MCP configuration given means no MCP servers load. Read, Grep and Glob are the only tools, so Claude Code can't run commands, git included, and can't edit or write files. That's why &lt;code&gt;review.sh&lt;/code&gt; puts the commits and the diff into the prompt. It also refuses to start when &lt;code&gt;claude --help&lt;/code&gt; doesn't list &lt;code&gt;--restricted&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The Claude Code review shuts MCP out with a flag in the call, while the Codex review starts whatever servers &lt;code&gt;config.toml&lt;/code&gt; leaves enabled.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://dev.to/iliyaoblakov/how-i-run-claude-code-and-codex-as-writer-and-reviewer-with-a-forced-verdict-line-2n49"&gt;writer and reviewer post&lt;/a&gt; walks through the swap.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you ship an MCP connector, get it reviewed
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;enabled = false&lt;/code&gt; switch is for MCP servers you run. If you ship an MCP connector, sort your own tools the same way, by whether they change data: &lt;code&gt;readOnlyHint: true&lt;/code&gt; on every read-only tool, &lt;code&gt;destructiveHint: true&lt;/code&gt; on every tool that modifies or deletes data, and read and write tools kept separate.&lt;/p&gt;

&lt;p&gt;Those items are on Anthropic's connector pre-submission checklist. My &lt;a href="https://rayastats.com/en/ai-connector-review" rel="noopener noreferrer"&gt;MCP connector review&lt;/a&gt; is an independent review of an existing MCP connector against that checklist, before it goes to customers or the Claude directory. Every tool goes through the MCP Inspector, and through Claude as a custom connector where hosting allows it. You get:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A checklist report.&lt;/li&gt;
&lt;li&gt;Tests of every tool with valid input, bad input, denied access and an upstream error.&lt;/li&gt;
&lt;li&gt;A two-user permission boundary check.&lt;/li&gt;
&lt;li&gt;Each finding with severity, impact, reproduction steps and a proposed fix.&lt;/li&gt;
&lt;li&gt;One retest after you fix the findings.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Anthropic doesn't run, approve or certify this review, and the review can't guarantee a Community or Verified listing. If the server isn't built yet, the &lt;a href="https://rayastats.com/en/ai-connector-build" rel="noopener noreferrer"&gt;MCP build&lt;/a&gt; puts one over your existing API.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://rayastats.com/en/two-engines-kit" rel="noopener noreferrer"&gt;Two Engines Kit&lt;/a&gt; is a set of files for working with Claude Code and Codex the way this post does, and I've been building it since the first Codex and Claude CLIs.&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>codex</category>
      <category>mcp</category>
      <category>ai</category>
    </item>
    <item>
      <title>ChatGPT named one Dallas med spa in 3, 1 and 4 of 8 answers on 3 days</title>
      <dc:creator>Iliya Oblakov</dc:creator>
      <pubDate>Tue, 10 Nov 2026 06:10:51 +0000</pubDate>
      <link>https://dev.to/iliyaoblakov/chatgpt-named-one-dallas-med-spa-in-3-1-and-4-of-8-answers-on-3-days-330h</link>
      <guid>https://dev.to/iliyaoblakov/chatgpt-named-one-dallas-med-spa-in-3-1-and-4-of-8-answers-on-3-days-330h</guid>
      <description>&lt;p&gt;Google's AI Overview gave the wrong Saturday opening time for a Dallas med spa on three days in a row. On the same three days, ChatGPT named that med spa in 3, then 1, then 4 answers to the same 8 questions.&lt;/p&gt;

&lt;p&gt;If you check your own business by asking once, you get one day's answer and nothing to compare it to.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;This was a sample audit on a real Dallas med spa in October 2026. It isn't a client of mine, so it stays anonymous.&lt;/p&gt;

&lt;p&gt;I asked ChatGPT and Google AI Overviews the same 10 questions on Friday 23, Saturday 24 and Sunday 25 October. Every run used the same wording in a new chat or a new search, logged out, with no follow-up questions.&lt;/p&gt;

&lt;p&gt;Eight were general questions a new client would ask, such as the best med spas in Dallas, laser hair removal packages or microneedling. Two asked about the med spa by name.&lt;/p&gt;

&lt;p&gt;I count the two groups separately. An engine almost always finds a business when you type its name, so mixing them would make the numbers look better than they are.&lt;/p&gt;

&lt;h2&gt;
  
  
  ChatGPT gave a different total each day
&lt;/h2&gt;

&lt;p&gt;The laser hair removal packages question held steady. ChatGPT listed the med spa first on all three days, with a link to its site.&lt;/p&gt;

&lt;p&gt;Three other questions moved:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Microneedling:&lt;/strong&gt; third on Friday, missing on Saturday, third again on Sunday.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Natural looking Botox:&lt;/strong&gt; on Friday the med spa showed up only as a pin on the answer's map and was left out of the written list. It was missing on Saturday. On Sunday it was listed first, with a Yelp page as the source instead of its own website.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chemical peel for acne scars:&lt;/strong&gt; named only on Sunday, in second place.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Friday's total of 3 counts that map pin. On the other four general questions, ChatGPT didn't name the med spa on any of the three days.&lt;/p&gt;

&lt;h2&gt;
  
  
  Google AI Overviews named it on 1 of 8 each day
&lt;/h2&gt;

&lt;p&gt;Google's AI Overview named the med spa on 1 of the 8 general questions each day, and it was the microneedling question every time. The position moved from 4th on Friday to 2nd on Saturday and 3rd on Sunday. The Overview linked to the med spa's site each time.&lt;/p&gt;

&lt;p&gt;On the Botox question, the med spa was first in Google's map pack on all three days. The AI Overview at the top of the same page never named it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The wrong Saturday hours came back on every run
&lt;/h2&gt;

&lt;p&gt;I asked for the med spa's address, phone number and opening hours. Google's AI Overview gave a fixed Saturday opening time on all three days. The med spa's own website says Saturday is by appointment only, and the time Google gave matches a third-party directory listing. ChatGPT got Saturday right on all three days, from the med spa's contact page.&lt;/p&gt;

&lt;p&gt;One run can't tell you whether a wrong answer is a one-off or something the engine keeps repeating. Three runs with the same wrong hours point at a source. So the first fix in my sample report was to make the Saturday hours match on the website, the Google Business Profile and the directory listings, and to ask the directory to correct its entry.&lt;/p&gt;

&lt;h2&gt;
  
  
  A question without the city got answers for Illinois
&lt;/h2&gt;

&lt;p&gt;One of my questions named two Dallas neighbourhoods and left out the city. ChatGPT answered it for towns with the same names in Illinois on all three days. Google did the same on Friday and Saturday, and placed it in Texas on Sunday.&lt;/p&gt;

&lt;p&gt;I kept the wording unchanged so the three runs stayed comparable. Every question in a real audit now has the city written into it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this sample can't tell you
&lt;/h2&gt;

&lt;p&gt;It covers one med spa in one city over three days. Perplexity and Gemini were blocked while logged out, so they have no results. The Claude chatbot was run on only one of the three days, so it has no totals here.&lt;/p&gt;

&lt;p&gt;The connection traced to Bulgaria on Friday and to a US exit on Saturday and Sunday, and that can change what an engine shows. "Not named" means not named in these answers. The sample doesn't show why an engine picked one business over another.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run the check on your own business
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Write 8 general questions a new client would ask, with your city in each one, plus 2 questions about your business by name.&lt;/li&gt;
&lt;li&gt;Ask each one in a new chat or a new search, logged out and with no follow-up, on 3 different days.&lt;/li&gt;
&lt;li&gt;For each answer, log whether you were named, your position, whether it linked to your site, which other businesses were named and whether the facts about you were right.&lt;/li&gt;
&lt;li&gt;Count the general and the by-name questions separately, and keep each day's total next to the others, for example 3 of 8, 1 of 8 and 4 of 8.&lt;/li&gt;
&lt;li&gt;Fix the facts that come back wrong on every run first.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://rayastats.com/en/blog/check-if-chatgpt-names-your-local-business" rel="noopener noreferrer"&gt;How to check whether ChatGPT and Google AI Overviews name your local business&lt;/a&gt; has the columns and the weekly routine in more detail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get the audit for your business
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://rayastats.com/en/ai-visibility-audit" rel="noopener noreferrer"&gt;AI Visibility Audit&lt;/a&gt; uses this method on your business. Your city is written into every question, and each question runs 3 times in each engine, on 3 different days. You see all 3 results side by side, with the fixes that come out of them.&lt;/p&gt;

&lt;p&gt;For a quick first look, start with the &lt;a href="https://rayastats.com/en/ai-visibility-audit/free-city-check" rel="noopener noreferrer"&gt;free AI city check&lt;/a&gt;. It asks ChatGPT and Google AI Overviews 3 client questions with your city in each, once in each engine, and I email you who they named.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>seo</category>
      <category>chatgpt</category>
      <category>smallbusiness</category>
    </item>
    <item>
      <title>How I run Claude Code and Codex as writer and reviewer, with a forced verdict line</title>
      <dc:creator>Iliya Oblakov</dc:creator>
      <pubDate>Tue, 10 Nov 2026 06:10:25 +0000</pubDate>
      <link>https://dev.to/iliyaoblakov/how-i-run-claude-code-and-codex-as-writer-and-reviewer-with-a-forced-verdict-line-2n49</link>
      <guid>https://dev.to/iliyaoblakov/how-i-run-claude-code-and-codex-as-writer-and-reviewer-with-a-forced-verdict-line-2n49</guid>
      <description>&lt;p&gt;Codex reviews each Claude Code commit in a read-only sandbox, and any review that doesn't end on &lt;code&gt;Verdict: ACCEPT&lt;/code&gt; or &lt;code&gt;Verdict: CHANGES REQUIRED&lt;/code&gt; counts as failed. So does a crash. My merge check also refuses an ACCEPT that came from the engine that wrote the code.&lt;/p&gt;

&lt;p&gt;The writer answers every finding, each new commit gets a fresh review, and later the roles swap so each engine spends time on both sides of the diff.&lt;/p&gt;

&lt;p&gt;This is the setup I use on my own projects, with the CLI flags taken from the official Claude Code and Codex docs. The reasons I &lt;a href="https://rayastats.com/en/blog/use-claude-code-and-codex-together" rel="noopener noreferrer"&gt;use Claude Code and Codex together&lt;/a&gt; are in an earlier post.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Give each engine one job
&lt;/h2&gt;

&lt;p&gt;Claude Code is the default writer. Writing is where context piles up: the plan, the files touched, the tests run. A review needs far less, only the diff and the project rules. A reviewer trained by a different company also has different blind spots, and that's why I use two engines instead of asking one to check itself.&lt;/p&gt;

&lt;p&gt;Start the writer in its own git worktree, so its edits never touch the checkout you review from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude -w feature-auth
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;-w&lt;/code&gt; flag starts Claude Code in an isolated worktree at &lt;code&gt;&amp;lt;repo&amp;gt;/.claude/worktrees/&amp;lt;name&amp;gt;&lt;/code&gt;. Reviews always run on a commit, never on loose edits, so tell the writer to commit each finished change and stop.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Run Codex as a read-only reviewer
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;codex exec&lt;/code&gt; is the non-interactive mode of the Codex CLI, and its default sandbox is read-only. I pass the sandbox flag anyway, so nobody has to remember the default:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;codex &lt;span class="nb"&gt;exec&lt;/span&gt; -s read-only -c &lt;span class="nv"&gt;model_reasoning_effort&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;high \
  --color never --output-last-message .ai/last-review.md \
  - &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; review-prompt.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;-s read-only&lt;/code&gt; lets Codex read files and run read-only commands such as &lt;code&gt;git diff&lt;/code&gt;, but not write.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-c&lt;/code&gt; overrides any config key for one run. Here it sets the documented &lt;code&gt;model_reasoning_effort&lt;/code&gt; key to &lt;code&gt;high&lt;/code&gt;. The review script in the Two Engines Kit defaults to &lt;code&gt;xhigh&lt;/code&gt;, one level above.&lt;/li&gt;
&lt;li&gt;The output-last-message flag saves only the final message to &lt;code&gt;.ai/last-review.md&lt;/code&gt;, and the verdict is read from that file.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;-&lt;/code&gt; on the last line makes Codex read the prompt from standard input.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If an MCP server in &lt;code&gt;~/.codex/config.toml&lt;/code&gt; can change things outside the sandbox, set &lt;code&gt;enabled = false&lt;/code&gt; on its table for the review run.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Force a verdict line
&lt;/h2&gt;

&lt;p&gt;My review prompt asks Codex to end on one of two exact lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Verdict: ACCEPT
Verdict: CHANGES REQUIRED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It sets four more rules:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Every finding carries a severity (blocker, should-fix or nit), a file and line, and a concrete failure scenario.&lt;/li&gt;
&lt;li&gt;Blockers and should-fixes need a concrete, reproducible failure. Style preferences stay at nit level.&lt;/li&gt;
&lt;li&gt;Codex lists every finding in this one pass, so the next round doesn't surface old problems.&lt;/li&gt;
&lt;li&gt;The review stays under 600 words.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The review script reads the verdict only from that saved final message, never from the log. It also checks that Codex exited with code 0 and that the checkout is unchanged afterwards. A crash or a missing verdict line counts as a failed review. The result is &lt;a href="https://rayastats.com/en/blog/review-verdict-saved-under-commit-hash" rel="noopener noreferrer"&gt;saved under the commit hash&lt;/a&gt; it reviewed, so a later commit can't borrow an old ACCEPT.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Make the writer answer every finding
&lt;/h2&gt;

&lt;p&gt;The reviewer has no say in what gets merged beyond its findings. The writer and the person running the loop decide.&lt;/p&gt;

&lt;p&gt;For each finding, the writer either fixes it in a new commit or disputes it with evidence: a test that passes, a file and line that shows the case is handled, or a command whose output contradicts the claim. Don't let it accept every finding by default. A reviewer can be wrong, and a fix for a problem that doesn't exist adds risk of its own.&lt;/p&gt;

&lt;p&gt;I follow three round rules from the kit workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Round 1 lists everything.&lt;/li&gt;
&lt;li&gt;Round 2 confirms that the fixes landed.&lt;/li&gt;
&lt;li&gt;From round 3 on, only blockers and security findings hold the merge. Nits go into a follow-ups file to handle later.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I wrote more about briefs and disagreement in &lt;a href="https://rayastats.com/en/blog/how-to-make-codex-review-claude-code-commits" rel="noopener noreferrer"&gt;how I brief Codex to review Claude Code commits&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Swap the roles
&lt;/h2&gt;

&lt;p&gt;In my gate, setting &lt;code&gt;BUILDER=codex&lt;/code&gt; flips the roles, so Claude Code reviews and Codex writes.&lt;/p&gt;

&lt;p&gt;As the writer, Codex needs a sandbox that allows edits inside the workspace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;codex &lt;span class="nb"&gt;exec&lt;/span&gt; --sandbox workspace-write \
  - &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; task.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As the reviewer, Claude Code runs in print mode with a short list of read-only tools. The gate makes this call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude -p &lt;span class="s2"&gt;"Do the review described on standard input."&lt;/span&gt; --restricted --strict-mcp-config \
  --permission-mode dontAsk --tools &lt;span class="s2"&gt;"Read,Grep,Glob"&lt;/span&gt; --allowedTools &lt;span class="s2"&gt;"Read,Grep,Glob"&lt;/span&gt; \
  --disallowedTools &lt;span class="s2"&gt;"Bash,Edit,MultiEdit,Write,NotebookEdit,WebFetch,WebSearch,Task,Agent"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With only Read, Grep and Glob available, Claude Code can't run commands, git included, and can't edit or write files. That's why the gate puts the commits and the full diff into the prompt itself. The strict MCP config flag with no MCP config loads no MCP servers, and permission mode &lt;code&gt;dontAsk&lt;/code&gt; denies anything that would otherwise ask for approval.&lt;/p&gt;

&lt;p&gt;The merge check I opened with refuses an ACCEPT from whichever engine wrote the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Keep one rulebook for both engines
&lt;/h2&gt;

&lt;p&gt;Claude Code reads &lt;code&gt;CLAUDE.md&lt;/code&gt; and Codex reads &lt;code&gt;AGENTS.md&lt;/code&gt;. Put the same short section in both:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Commit each change and stop.&lt;/li&gt;
&lt;li&gt;Never merge.&lt;/li&gt;
&lt;li&gt;Stay read-only when reviewing.&lt;/li&gt;
&lt;li&gt;Leave the review records folder alone.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Codex limits how many bytes of &lt;code&gt;AGENTS.md&lt;/code&gt; it loads (the &lt;code&gt;project_doc_max_bytes&lt;/code&gt; setting), so keep that section short and put long reference material elsewhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the loop doesn't replace
&lt;/h2&gt;

&lt;p&gt;Your tests have to run on the exact commit that got the ACCEPT. My gate checks for both a test pass and an ACCEPT on that commit before it allows a merge. A reviewer on high reasoning effort catches a lot, but it only reads the diff you gave it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running it on your own repository
&lt;/h2&gt;

&lt;p&gt;If you want it running without doing the wiring yourself, I set up Codex to review your Claude Code commits before they merge. The &lt;a href="https://rayastats.com/en/ai-code-review" rel="noopener noreferrer"&gt;Codex review setup&lt;/a&gt; is written from scratch for each repository, its stack and its conventions, then the first review runs on one of your real branches. Nothing is copied from a template or from the Two Engines Kit.&lt;/p&gt;

&lt;p&gt;To start from the files I use and wire it up yourself, get the &lt;a href="https://rayastats.com/en/two-engines-kit" rel="noopener noreferrer"&gt;Two Engines Kit&lt;/a&gt;. It packages the review script, the gate, the merge check, the review prompt and the workflow rules from this post, for WSL2, Linux and macOS. It's the setup I run on my own projects, built on information gathered since the first Codex and Claude CLIs.&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>codex</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Claude Code Review never blocks a merge: GitHub App vs Action vs /code-review vs a local gate</title>
      <dc:creator>Iliya Oblakov</dc:creator>
      <pubDate>Tue, 10 Nov 2026 06:06:49 +0000</pubDate>
      <link>https://dev.to/iliyaoblakov/claude-code-review-never-blocks-a-merge-github-app-vs-action-vs-code-review-vs-a-local-gate-45m8</link>
      <guid>https://dev.to/iliyaoblakov/claude-code-review-never-blocks-a-merge-github-app-vs-action-vs-code-review-vs-a-local-gate-45m8</guid>
      <description>&lt;p&gt;Anthropic's managed Code Review never approves or blocks a pull request. Its check run concludes as neutral or skipped, never as a failure, and the docs say you can merge without responding to a finding unless your repository's own rules require it.&lt;/p&gt;

&lt;p&gt;So before you pick how Claude Code reviews your code, decide what the review should be able to stop. You have four paths: managed Code Review through the Claude GitHub App, the Claude Code GitHub Action, the &lt;code&gt;/code-review&lt;/code&gt; command, and a local gate that runs your tests and saves a second engine's verdict under the commit before anything merges.&lt;/p&gt;

&lt;p&gt;Everything below about the first three comes from Anthropic's docs as of 9 Nov 2026. The fourth is my local gate, a version of how I make Claude Code and Codex check each other on my own projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four paths side by side
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Managed Code Review&lt;/th&gt;
&lt;th&gt;GitHub Action&lt;/th&gt;
&lt;th&gt;&lt;code&gt;/code-review&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;Local gate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Started by&lt;/td&gt;
&lt;td&gt;Pull request activity or an &lt;code&gt;@claude review&lt;/code&gt; comment&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;@claude&lt;/code&gt; mentions, or a prompt on the GitHub events you configure&lt;/td&gt;
&lt;td&gt;You, on your branch&lt;/td&gt;
&lt;td&gt;You, after each commit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runs on&lt;/td&gt;
&lt;td&gt;Anthropic infrastructure&lt;/td&gt;
&lt;td&gt;Your own CI runners&lt;/td&gt;
&lt;td&gt;Your Claude Code session&lt;/td&gt;
&lt;td&gt;Your machine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Who can use it&lt;/td&gt;
&lt;td&gt;Team and Enterprise, once an Owner turns it on&lt;/td&gt;
&lt;td&gt;Anyone with a Claude API key or cloud provider credentials&lt;/td&gt;
&lt;td&gt;Anyone, on any plan&lt;/td&gt;
&lt;td&gt;Anyone with bash 4 or newer and git, plus the Claude Code and Codex CLIs for real reviews&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Changes code&lt;/td&gt;
&lt;td&gt;Read-only, and it doesn't push commits&lt;/td&gt;
&lt;td&gt;Can implement changes and push commits&lt;/td&gt;
&lt;td&gt;With &lt;code&gt;--fix&lt;/code&gt;, through your permission mode&lt;/td&gt;
&lt;td&gt;The builder commits, and the reviewer runs read-only&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Managed Code Review leaves the merge to your team
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://code.claude.com/docs/en/code-review" rel="noopener noreferrer"&gt;managed Code Review&lt;/a&gt;, several agents read each change, a verification step checks what they find, and the findings arrive as inline comments ranked by severity, with a summary and a check run named Claude Code Review. By default it looks for bugs that would break production and leaves formatting preferences and missing test coverage alone. It reads the diff and the surrounding code "without building or running your code", and it follows &lt;code&gt;REVIEW.md&lt;/code&gt; from the base or default branch, never from the pull request's own changes.&lt;/p&gt;

&lt;p&gt;You set how thorough it is with the review level (Quick, Standard or Deep), and in Anthropic's words "a more thorough review costs more". The &lt;a href="https://code.claude.com/docs/en/code-review-costs" rel="noopener noreferrer"&gt;costs page&lt;/a&gt; has the details.&lt;/p&gt;

&lt;p&gt;A finished review doesn't fail a required check. To make a finding hold a merge, the docs describe reading the check-run details in your own CI and gating the merge there.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;/code-review&lt;/code&gt; runs in your own session
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://code.claude.com/docs/en/code-review-local" rel="noopener noreferrer"&gt;&lt;code&gt;/code-review&lt;/code&gt;&lt;/a&gt; finds bugs in your branch's changes from inside Claude Code, and it counts toward your plan's usage or API billing the same way the rest of the session does. With no target it reviews your branch's commits ahead of its upstream plus your uncommitted changes. You can also point it at a pull request or a path, and &lt;code&gt;/review&lt;/code&gt; is an alias.&lt;/p&gt;

&lt;p&gt;It follows your &lt;code&gt;CLAUDE.md&lt;/code&gt;, including in a non-interactive &lt;code&gt;-p&lt;/code&gt; run, and doesn't read &lt;code&gt;REVIEW.md&lt;/code&gt;. Without &lt;code&gt;--fix&lt;/code&gt; its instructions ask for a report only. With &lt;code&gt;--fix&lt;/code&gt; they also ask it to apply the findings to your working tree, through your permission mode.&lt;/p&gt;

&lt;p&gt;To review a pull request without managed Code Review or a workflow file, point &lt;code&gt;/code-review&lt;/code&gt; at it and add &lt;code&gt;--comment&lt;/code&gt;. The findings land on the pull request as inline comments.&lt;/p&gt;

&lt;h2&gt;
  
  
  The GitHub Action can push commits
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://code.claude.com/docs/en/github-actions" rel="noopener noreferrer"&gt;Claude Code GitHub Action&lt;/a&gt; runs Claude Code in your GitHub Actions workflows. Mention &lt;code&gt;@claude&lt;/code&gt; in a pull request or issue comment and it can analyze code, implement changes and push commits. Managed Code Review never pushes, and the Action's own docs send you to Code Review for automatic reviews without a workflow file.&lt;/p&gt;

&lt;p&gt;Setup is &lt;code&gt;/install-github-app&lt;/code&gt;, or a manual install of the Claude GitHub App, a secret and a workflow file, and you need admin access to the repository. Its review workflow posts an inline comment on the pull request for each issue, or one summary comment when it finds none.&lt;/p&gt;

&lt;p&gt;If you run the Action in interactive mode with its default &lt;code&gt;@claude&lt;/code&gt; trigger phrase next to managed Code Review, an &lt;code&gt;@claude review&lt;/code&gt; comment starts both. Set the Action's &lt;code&gt;trigger_phrase&lt;/code&gt; input to a different phrase to keep them apart.&lt;/p&gt;

&lt;h2&gt;
  
  
  The local gate ties tests and a verdict to one commit
&lt;/h2&gt;

&lt;p&gt;The local gate saves a test pass and a second engine's verdict under one commit hash, and refuses the merge without both. In the loop, Claude Code writes the code, commits each change on a branch and stops, and Codex reviews that commit read-only. Three scripts do the checking:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;gate.sh&lt;/code&gt; runs your test command on a clean checkout and writes &lt;code&gt;.ai/tests/&amp;lt;sha&amp;gt;.pass&lt;/code&gt; only when the command passed, left the checkout unchanged and didn't report a run of zero tests.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;review.sh&lt;/code&gt; sends the commits and the diff to the engine that didn't write them and saves the verdict as &lt;code&gt;.ai/reviews/&amp;lt;sha12&amp;gt;.md&lt;/code&gt;, with a &lt;code&gt;Head:&lt;/code&gt; line holding the full hash.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;merge-check.sh&lt;/code&gt; refuses unless the checkout is clean and HEAD has a pass record and an ACCEPT from a reviewer that isn't the builder. It never merges.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Codex reviews through &lt;code&gt;codex exec -s read-only&lt;/code&gt;, so it can read files and can't write them. &lt;code&gt;BUILDER=codex&lt;/code&gt; swaps the roles, and Claude Code then reviews with Read, Grep and Glob as its only tools.&lt;/p&gt;

&lt;p&gt;These lines are from one run of the repo's demo, where a stand-in reviewer gives canned answers and no model is called:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;merge-check: REFUSED: no test pass record for 2cef9bbe04c3; run gate.sh
gate: PASS on 2cef9bbe04c3, record .ai/tests/2cef9bbe04c3dd4727d2455e07c0ec420ecec7af.pass
merge-check: REFUSED: no review verdict for 2cef9bbe04c3; run review.sh
Verdict: CHANGES REQUIRED
- blocker, sum.sh:3: the loop starts at the second argument, so 'bash sum.sh 3 4' prints 4, not 7. test.sh only checks 'sum 0 4', where the bug cannot show.
merge-check: REFUSED: the verdict for 2cef9bbe04c3 is not a single ACCEPT
merge-check: REFUSED: no test pass record for 525000aeac20; run gate.sh
merge-check: OK: 525000aeac20 has a test pass and an ACCEPT from codex (builder claude)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tests passed on a commit with a bug in it. The review asked for changes, and the fix commit started with no evidence, so it needed its own test pass and its own ACCEPT.&lt;/p&gt;

&lt;p&gt;All three scripts also refuse a dirty checkout, because a test can pass on a file that isn't in the commit, and a reviewer can open a file that differs from the commit it judges:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gate: REFUSED: uncommitted changes, untracked files or flagged files (or git failed); commit, remove or unflag them first
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://rayastats.com/en/blog/review-verdict-saved-under-commit-hash" rel="noopener noreferrer"&gt;Why the verdict is saved under the commit hash&lt;/a&gt; and &lt;a href="https://rayastats.com/en/blog/review-gate-refuses-dirty-working-tree" rel="noopener noreferrer"&gt;why the gate refuses a dirty working tree&lt;/a&gt; go through each check.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the gate doesn't do
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;It doesn't make the code correct. A review can miss things, and tests only check what they test.&lt;/li&gt;
&lt;li&gt;It guards against merging the wrong commit and isn't a security boundary: anything with write access to the repository can write files under &lt;code&gt;.ai/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It doesn't push, merge or talk to GitHub.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Should Claude Code and Codex review each other?
&lt;/h2&gt;

&lt;p&gt;Managed Code Review chooses the Claude models it runs on, so a change Claude Code wrote gets reviewed by models from the same company. For my gate I picked a reviewer from a different company because it has different blind spots, and on one pull request the first Codex review came back with 2 blockers and 8 should-fix findings. &lt;code&gt;merge-check.sh&lt;/code&gt; refuses a verdict whose reviewer is the same engine as the builder, so no engine approves its own commit. The prompt I send Codex is in &lt;a href="https://rayastats.com/en/blog/how-to-make-codex-review-claude-code-commits" rel="noopener noreferrer"&gt;how to make Codex review Claude Code's commits&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which path fits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;You're on Team or Enterprise and want a review on every pull request without maintaining a workflow: managed Code Review, with the merge decision left to your team.&lt;/li&gt;
&lt;li&gt;You want a review of your branch before you push, on any plan: &lt;code&gt;/code-review&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;You want Claude Code to answer &lt;code&gt;@claude&lt;/code&gt; comments, implement changes and push commits from your CI: the GitHub Action.&lt;/li&gt;
&lt;li&gt;You want the merge refused until the commit has its own test pass and an ACCEPT from the other engine: the local gate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The local gate never talks to GitHub, so you can run it before the push and keep managed Code Review on the pull request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Set up a local gate
&lt;/h2&gt;

&lt;p&gt;If you want the merge itself gated, the &lt;a href="https://rayastats.com/en/two-engines-kit" rel="noopener noreferrer"&gt;Two Engines Kit&lt;/a&gt; is the self-install path and the workflow I use on my own projects. Its &lt;code&gt;merge-pr.sh&lt;/code&gt; refuses to call GitHub without a local pass record for the exact commit and tree, then pushes, opens the pull request, merges only if the head hasn't moved, and checks that the merged tree matches the tested one. With the review switch on (&lt;code&gt;REQUIRE_REVIEW=1&lt;/code&gt;) it also refuses a commit without a saved ACCEPT. I added that switch after a night in October when I found four test-only commits that had never been reviewed, one of them already merged. The kit is built on information gathered since the first Codex and Claude CLIs.&lt;/p&gt;

&lt;p&gt;If you'd rather have the loop configured for you, that's the &lt;a href="https://rayastats.com/en/ai-code-review" rel="noopener noreferrer"&gt;Codex review setup&lt;/a&gt;. I set up Codex to review Claude Code's commits in up to three of your repositories, written from scratch for each one, with none of the kit's files. Your tests run before every merge, and with review enforcement on, a commit Codex rejects doesn't merge. The first review runs on one of your real branches, with a recorded walkthrough of the findings.&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>codex</category>
      <category>github</category>
      <category>ai</category>
    </item>
    <item>
      <title>Codex keeps returning CHANGES REQUIRED: sort the finding before Claude Code edits again</title>
      <dc:creator>Iliya Oblakov</dc:creator>
      <pubDate>Mon, 09 Nov 2026 19:59:08 +0000</pubDate>
      <link>https://dev.to/iliyaoblakov/codex-keeps-returning-changes-required-sort-the-finding-before-claude-code-edits-again-1c86</link>
      <guid>https://dev.to/iliyaoblakov/codex-keeps-returning-changes-required-sort-the-finding-before-claude-code-edits-again-1c86</guid>
      <description>&lt;p&gt;One more Claude Code edit won't end a Codex review that keeps returning &lt;code&gt;CHANGES REQUIRED&lt;/code&gt;. Each speculative patch gives the reviewer new code to comment on, so every pass comes back with new comments. Sort the finding before anyone touches the code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://rayastats.com/en/blog/how-to-make-codex-review-claude-code-commits" rel="noopener noreferrer"&gt;How to make Codex review Claude Code's commits&lt;/a&gt; covers the hooks, the worktrees and the loop itself. This one starts after setup, with a loop that runs and won't reach &lt;code&gt;ACCEPT&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sort every finding into one of five classes
&lt;/h2&gt;

&lt;p&gt;My reviewer answers every line of the brief and every house rule with &lt;code&gt;met&lt;/code&gt;, &lt;code&gt;not met&lt;/code&gt; or &lt;code&gt;not checkable&lt;/code&gt;. A finding outside that list needs a concrete failure: the input or steps, plus what goes wrong.&lt;/p&gt;

&lt;p&gt;Each finding gets a class and a response before Claude Code sees another edit request:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Finding class&lt;/th&gt;
&lt;th&gt;Response&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Brief or house-rule violation&lt;/td&gt;
&lt;td&gt;Fix it, or ask a person when the requirement itself is disputed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failure outside the brief&lt;/td&gt;
&lt;td&gt;Block only with concrete reproduction details&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reviewer preference&lt;/td&gt;
&lt;td&gt;Move to follow-ups unless a written rule requires it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unverifiable claim&lt;/td&gt;
&lt;td&gt;Ask for evidence before patching anything&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Missing, duplicate or malformed verdict&lt;/td&gt;
&lt;td&gt;Operational failure: rerun on purpose and don't count it as a review round&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  When the review itself breaks
&lt;/h2&gt;

&lt;p&gt;A crash, an empty final message, or a missing, duplicate or malformed verdict leaves no saved verdict, and with no saved verdict my gate refuses the merge.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;review.sh&lt;/code&gt; exits &lt;code&gt;0&lt;/code&gt; for a saved &lt;code&gt;ACCEPT&lt;/code&gt;, &lt;code&gt;2&lt;/code&gt; for a saved &lt;code&gt;CHANGES REQUIRED&lt;/code&gt; and &lt;code&gt;1&lt;/code&gt; when nothing was saved. Exit &lt;code&gt;1&lt;/code&gt; says nothing about the code, so Claude Code has nothing to fix. The gate doesn't retry on its own. Read the log, then rerun the review on purpose. &lt;a href="https://rayastats.com/en/blog/review-gate-no-verdict-crash" rel="noopener noreferrer"&gt;What the gate does when a review crashes or returns no verdict&lt;/a&gt; covers that case.&lt;/p&gt;

&lt;h2&gt;
  
  
  A blocker needs a failure you can reproduce
&lt;/h2&gt;

&lt;p&gt;In my setup, a finding stands when it comes with a reproducible failure. Without one, it goes to follow-ups. The rule exists because a reviewer can be wrong, and tests only check what they test.&lt;/p&gt;

&lt;p&gt;For a blocker, ask Codex for three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The input or steps that trigger it&lt;/li&gt;
&lt;li&gt;The expected behavior&lt;/li&gt;
&lt;li&gt;The actual behavior&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With those, Claude Code fixes a demonstrated failure. Without them, it patches toward a review that grows with every pass.&lt;/p&gt;

&lt;p&gt;Tests get the same treatment. In the &lt;a href="https://rayastats.com/en/blog/review-gate-weakened-deleted-tests" rel="noopener noreferrer"&gt;weakened-test example&lt;/a&gt;, &lt;code&gt;add(2, 3)&lt;/code&gt; returns 6 and the weakened assertion accepts it, so the suite passes with the bug in place. A new test should fail on the old behavior. A loosened assertion with no stated reason can draw &lt;code&gt;CHANGES REQUIRED&lt;/code&gt;, and a review can miss it too, so a passing review doesn't guarantee correct code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Brief violations and reviewer preferences
&lt;/h2&gt;

&lt;p&gt;A brief violation is a required change. A preference is a technically plausible alternative to code that already meets the brief. The per-line check keeps Codex anchored to that difference: once every brief line and house rule is &lt;code&gt;met&lt;/code&gt;, a remaining comment needs a reproducible failure to block.&lt;/p&gt;

&lt;p&gt;In a made-up example, Codex suggests renaming &lt;code&gt;get_rows&lt;/code&gt; to &lt;code&gt;fetch_rows&lt;/code&gt;. Every brief line is &lt;code&gt;met&lt;/code&gt;, nothing fails and no house rule covers the name, so the comment goes to follow-ups. If a house rule did set the naming pattern, the same comment would be a &lt;code&gt;not met&lt;/code&gt; line, and Claude Code would fix it in this round.&lt;/p&gt;

&lt;h2&gt;
  
  
  Later rounds get a narrower packet
&lt;/h2&gt;

&lt;p&gt;Early rounds can raise anything relevant. Later rounds raise only merge blockers, and smaller points go on the follow-ups list.&lt;/p&gt;

&lt;p&gt;Every round reviews the current commit. My verdicts are saved under the exact commit that was reviewed, so a new commit needs a new review, and an &lt;code&gt;ACCEPT&lt;/code&gt; for an old commit can't cover changed code (&lt;a href="https://rayastats.com/en/blog/review-verdict-saved-under-commit-hash" rel="noopener noreferrer"&gt;why the verdict is saved under the commit hash&lt;/a&gt;). The narrowing happens in the packet Codex gets for each later round:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The unresolved findings&lt;/li&gt;
&lt;li&gt;What changed since the previous review&lt;/li&gt;
&lt;li&gt;The exact current commit and its diff&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A compact prompt for that packet:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Review the current commit. Recheck the unresolved findings and the changes since the previous review. Do not reopen settled preferences or unrelated code. A new blocker outside that scope needs concrete reproduction details.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A new blocker stays valid in any round when Codex identifies the failure and reproduces it, so narrowing doesn't hide regressions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pick a hard stop before round one
&lt;/h2&gt;

&lt;p&gt;Write the cap down before the first round. Tell Codex which round it's in, so it knows when the later-round rules apply.&lt;/p&gt;

&lt;p&gt;If the last allowed round returns &lt;code&gt;CHANGES REQUIRED&lt;/code&gt;, stop sending fixes. The verdict stays &lt;code&gt;CHANGES REQUIRED&lt;/code&gt; and the merge stays blocked, because a cap never turns the result into acceptance. A person then picks the next step: change the approach, split the change, or allow another round on purpose. Non-blockers wait in follow-ups and don't hold up the merge.&lt;/p&gt;

&lt;p&gt;Count only substantive rounds, so a rerun after an operational failure leaves the count alone. A new commit doesn't reset the count for the same change, or every fix would buy a fresh set of rounds.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the official Codex plugin already covers
&lt;/h2&gt;

&lt;p&gt;OpenAI publishes a &lt;a href="https://github.com/openai/codex-plugin-cc" rel="noopener noreferrer"&gt;Codex plugin for Claude Code&lt;/a&gt; with an optional review gate. The gate is a Stop hook: when a targeted Codex review of Claude Code's response finds issues, it blocks the stop so Claude Code can address them.&lt;/p&gt;

&lt;p&gt;The stop-gate prompt limits the review to the previous Claude Code turn that produced edits, requires blocking claims to be grounded in repository context or inspected tool output, and returns &lt;code&gt;ALLOW&lt;/code&gt; or &lt;code&gt;BLOCK&lt;/code&gt;. The prompt behind &lt;code&gt;/codex:adversarial-review&lt;/code&gt; asks for material findings and excludes style feedback, naming feedback, low-value cleanup and speculative concerns without evidence. Both prompts already apply the evidence bar from the sections above. The README warns that the gate can create a long-running loop between Claude Code and Codex, can drain usage limits quickly, and should be on only while you're monitoring the session.&lt;/p&gt;

&lt;p&gt;The documentation leaves the stopping policy to you: a cap on substantive rounds, how to resolve a disputed brief, how to narrow later rounds across repeated fix commits and where non-blockers go.&lt;/p&gt;

&lt;h2&gt;
  
  
  Disputed requirements and scope go to a person
&lt;/h2&gt;

&lt;p&gt;When Claude Code and Codex read an acceptance criterion two ways, each round repeats the disagreement in a new commit. The loop is missing product intent that nobody wrote down, and extra passes can't supply it.&lt;/p&gt;

&lt;p&gt;Keep the unresolved verdict and send the owner of the change one short question. Scope questions and anything a user will see go the same way. Codex supplies the evidence, the owner chooses the requirement, Claude Code implements that decision, and Codex reviews against it.&lt;/p&gt;

&lt;p&gt;The loop ends in one of two states: an &lt;code&gt;ACCEPT&lt;/code&gt; for the current commit, or a recorded blocked verdict plus a human decision about the next step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run these rules on your own repository
&lt;/h2&gt;

&lt;p&gt;The Two Engines Kit has the review prompts, round rules and commit-specific verdict records. Built on information gathered since the first Codex and Claude CLIs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://rayastats.com/en/two-engines-kit" rel="noopener noreferrer"&gt;Get the Two Engines Kit&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want the loop set up for you instead, I set up Codex to review your Claude Code commits before they merge. The review setup is written from scratch for your repository, its stack and its conventions, and the first review runs on one of your real branches. Nothing is copied from a template or from the Two Engines Kit.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://rayastats.com/en/ai-code-review" rel="noopener noreferrer"&gt;See the AI code review setup&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claudecode</category>
      <category>codex</category>
      <category>codereview</category>
    </item>
    <item>
      <title>AGENTS.md ignored in a monorepo? Claude Code and Codex walk the tree in opposite directions</title>
      <dc:creator>Iliya Oblakov</dc:creator>
      <pubDate>Mon, 09 Nov 2026 19:58:29 +0000</pubDate>
      <link>https://dev.to/iliyaoblakov/agentsmd-ignored-in-a-monorepo-claude-code-and-codex-walk-the-tree-in-opposite-directions-214n</link>
      <guid>https://dev.to/iliyaoblakov/agentsmd-ignored-in-a-monorepo-claude-code-and-codex-walk-the-tree-in-opposite-directions-214n</guid>
      <description>&lt;p&gt;Start Codex at the root of a monorepo and the &lt;code&gt;AGENTS.md&lt;/code&gt; in your package stays out of that run. Leave a &lt;code&gt;CLAUDE.md&lt;/code&gt; in any folder above your working directory and Claude Code, under its default setting, reads that file instead of &lt;code&gt;AGENTS.md&lt;/code&gt;. In both cases the &lt;code&gt;AGENTS.md&lt;/code&gt; sits where you put it.&lt;/p&gt;

&lt;p&gt;So when an &lt;code&gt;AGENTS.md&lt;/code&gt; looks ignored, check the path around it before you edit the file. Three things decide what loads: the folder you launched from, a file higher up that takes its place, and a size cap.&lt;/p&gt;

&lt;p&gt;The same problem shows up on Stack Overflow for other setups. A question about &lt;a href="https://stackoverflow.com/questions/79791061" rel="noopener noreferrer"&gt;GitHub Copilot reporting no instructions while AGENTS.md is in the repository&lt;/a&gt; had 1,584 views on 5 November 2026, and one about &lt;a href="https://stackoverflow.com/questions/79991176" rel="noopener noreferrer"&gt;AGENTS.md in a Gradle multi-module project&lt;/a&gt; had 149. This post covers Claude Code and Codex, the two I use, per their official docs as of 5 November 2026. For a file-by-file comparison of the two formats, see &lt;a href="https://rayastats.com/en/blog/claude-md-vs-agents-md" rel="noopener noreferrer"&gt;CLAUDE.md vs AGENTS.md&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Claude Code and Codex build the chain
&lt;/h2&gt;

&lt;p&gt;Take this monorepo, with the session started in &lt;code&gt;packages/api/src&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;repo/
  AGENTS.md
  packages/
    api/
      AGENTS.md
      src/
    web/
      AGENTS.md
      CLAUDE.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Codex, per its &lt;a href="https://developers.openai.com/codex/guides/agents-md" rel="noopener noreferrer"&gt;AGENTS.md guide&lt;/a&gt;, first reads one global file from its home folder (&lt;code&gt;~/.codex&lt;/code&gt; unless you set &lt;code&gt;CODEX_HOME&lt;/code&gt;): &lt;code&gt;AGENTS.override.md&lt;/code&gt; if it exists, otherwise &lt;code&gt;AGENTS.md&lt;/code&gt;. Then it starts at the project root, typically the Git root, and walks down to the folder you launched from. In each folder it takes at most one file, checking &lt;code&gt;AGENTS.override.md&lt;/code&gt;, then &lt;code&gt;AGENTS.md&lt;/code&gt;, then any names in &lt;code&gt;project_doc_fallback_filenames&lt;/code&gt;. It joins the files root first, so the file closest to your folder comes last and overrides earlier guidance.&lt;/p&gt;

&lt;p&gt;Claude Code, per its &lt;a href="https://code.claude.com/docs/en/memory" rel="noopener noreferrer"&gt;memory docs&lt;/a&gt;, walks the other way: from your working directory up through every folder above it. If no &lt;code&gt;CLAUDE.md&lt;/code&gt;, &lt;code&gt;.claude/CLAUDE.md&lt;/code&gt; or &lt;code&gt;CLAUDE.local.md&lt;/code&gt; sits on that path, it reads every &lt;code&gt;AGENTS.md&lt;/code&gt; and &lt;code&gt;.claude/AGENTS.md&lt;/code&gt; it finds there at session start. An &lt;code&gt;AGENTS.md&lt;/code&gt; below your working directory loads later, when Claude Code opens a file in that subfolder with the Read tool, and only if the subfolder has no &lt;code&gt;CLAUDE.md&lt;/code&gt; of its own. Reading &lt;code&gt;AGENTS.md&lt;/code&gt; directly needs Claude Code v2.1.277 or later.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://agents.md/" rel="noopener noreferrer"&gt;agents.md site&lt;/a&gt; recommends nested files for monorepos and says the file closest to the edited file takes precedence. Claude Code and Codex both follow that layering and differ in when each file enters the session. With both started at the root of the example repo, Claude Code reads &lt;code&gt;packages/api/AGENTS.md&lt;/code&gt; once it opens a file in that package with the Read tool, and Codex leaves it out of the run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three files that hide an AGENTS.md
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;AGENTS.override.md&lt;/code&gt;. Codex checks it first in each folder and reads only one file there, so an override hides the &lt;code&gt;AGENTS.md&lt;/code&gt; beside it. Claude Code doesn't read override files at all. If Codex follows rules you can't find, look for an override higher in the tree or in &lt;code&gt;~/.codex&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;CLAUDE.md&lt;/code&gt;, &lt;code&gt;.claude/CLAUDE.md&lt;/code&gt; or &lt;code&gt;CLAUDE.local.md&lt;/code&gt; in your working directory or any folder above it. Under the default setting, Claude Code reads those instead of &lt;code&gt;AGENTS.md&lt;/code&gt;, even when the file sits in a parent folder above the repo. Your &lt;code&gt;~/.claude/CLAUDE.md&lt;/code&gt;, a managed &lt;code&gt;CLAUDE.md&lt;/code&gt; and &lt;code&gt;.claude/rules/&lt;/code&gt; files don't count: they load alongside &lt;code&gt;AGENTS.md&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A package &lt;code&gt;CLAUDE.md&lt;/code&gt;. In the example, &lt;code&gt;packages/web/CLAUDE.md&lt;/code&gt; makes Claude Code skip &lt;code&gt;packages/web/AGENTS.md&lt;/code&gt; when it works in that package. Codex reads &lt;code&gt;packages/web/AGENTS.md&lt;/code&gt; and ignores the &lt;code&gt;CLAUDE.md&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://developers.openai.com/codex/guides/agents-md" rel="noopener noreferrer"&gt;Codex troubleshooting list&lt;/a&gt; adds two causes. Codex skips empty files. A non-default &lt;code&gt;CODEX_HOME&lt;/code&gt; points it at a different home folder from the one you edited, so run &lt;code&gt;echo $CODEX_HOME&lt;/code&gt; before you debug the global file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where root rules and package rules go
&lt;/h2&gt;

&lt;p&gt;Put rules every package follows in the root &lt;code&gt;AGENTS.md&lt;/code&gt; and rules for one package in that package's &lt;code&gt;AGENTS.md&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If a package needs a &lt;code&gt;CLAUDE.md&lt;/code&gt; for lines only Claude Code should see, start that file with a bare &lt;code&gt;@AGENTS.md&lt;/code&gt; import. Claude Code reads the imported file first, then the lines below it, so Claude Code and Codex both get the package &lt;code&gt;AGENTS.md&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In a large monorepo, Claude Code's &lt;code&gt;claudeMdExcludes&lt;/code&gt; setting skips other teams' files by path or glob, and the docs say it covers the &lt;code&gt;AGENTS.md&lt;/code&gt; files Claude Code reads too. Put it in &lt;code&gt;.claude/settings.local.json&lt;/code&gt; to keep the exclusion on your machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the Codex chain under 32 KiB
&lt;/h2&gt;

&lt;p&gt;Codex stops adding files once the combined size of the chain reaches &lt;code&gt;project_doc_max_bytes&lt;/code&gt;, 32 KiB by default. Root files load first, so in a deep package the root file can use most of the budget and the package file, which loads last, gets cut. Raise the limit in &lt;code&gt;~/.codex/config.toml&lt;/code&gt; or move rules out of the root file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# ~/.codex/config.toml&lt;/span&gt;
&lt;span class="n"&gt;project_doc_max_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;65536&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude Code's docs target under 200 lines per &lt;code&gt;CLAUDE.md&lt;/code&gt;, and a file over 4 MiB is skipped. A subfolder &lt;code&gt;CLAUDE.md&lt;/code&gt; that loads on demand has a per-file character limit tied to the context window, and Claude Code receives only the start of a longer file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check what loaded, from the root and from a package
&lt;/h2&gt;

&lt;p&gt;This script only reads files. It prints the Codex chain with its combined size, any file that stops Claude Code reading &lt;code&gt;AGENTS.md&lt;/code&gt;, and the nested files below you. It doesn't cover names in &lt;code&gt;project_doc_fallback_filenames&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# Run from the folder you start Claude Code and Codex in.&lt;/span&gt;
&lt;span class="nv"&gt;root&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git rev-parse &lt;span class="nt"&gt;--show-toplevel&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="nv"&gt;here&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt; &lt;span class="nt"&gt;-P&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;home&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CODEX_HOME&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="p"&gt;/.codex&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Codex chain (global, then root down to here):"&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;f &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$home&lt;/span&gt;&lt;span class="s2"&gt;/AGENTS.override.md"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$home&lt;/span&gt;&lt;span class="s2"&gt;/AGENTS.md"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  &lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;total&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &amp;lt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;done
&lt;/span&gt;&lt;span class="nb"&gt;dirs&lt;/span&gt;&lt;span class="o"&gt;=()&lt;/span&gt; &lt;span class="nv"&gt;d&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$here&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; :&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do &lt;/span&gt;&lt;span class="nb"&gt;dirs&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$d&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;dirs&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$d&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$root&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;d&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$d&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;done
for &lt;/span&gt;d &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;dirs&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  for &lt;/span&gt;f &lt;span class="k"&gt;in &lt;/span&gt;AGENTS.override.md AGENTS.md&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$d&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  &lt;/span&gt;&lt;span class="nv"&gt;$d&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;total&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;total&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;0&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &amp;lt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$d&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="k"&gt;))&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;done
done
&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  combined bytes: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;total&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;0&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; of 32768 default"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Files that stop Claude Code reading AGENTS.md (here and above):"&lt;/span&gt;
&lt;span class="nv"&gt;d&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$here&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; :&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  for &lt;/span&gt;f &lt;span class="k"&gt;in &lt;/span&gt;CLAUDE.md .claude/CLAUDE.md CLAUDE.local.md&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$d&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$d&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.claude/CLAUDE.md"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  &lt;/span&gt;&lt;span class="nv"&gt;$d&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;done&lt;/span&gt;
  &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$d&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; / &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;d&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$d&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;done

&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Nested files below here (load later or only from their folder):"&lt;/span&gt;
find &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-mindepth&lt;/span&gt; 2 &lt;span class="se"&gt;\(&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'AGENTS*.md'&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'CLAUDE*.md'&lt;/span&gt; &lt;span class="se"&gt;\)&lt;/span&gt; &lt;span class="nt"&gt;-not&lt;/span&gt; &lt;span class="nt"&gt;-path&lt;/span&gt; &lt;span class="s1"&gt;'*/node_modules/*'&lt;/span&gt; &lt;span class="nt"&gt;-not&lt;/span&gt; &lt;span class="nt"&gt;-path&lt;/span&gt; &lt;span class="s1"&gt;'./.git/*'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then ask Codex directly. Its guide gives one command for the root and one for a package. Both turn approval prompts off, so run them only in a repo you trust:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;codex &lt;span class="nt"&gt;--ask-for-approval&lt;/span&gt; never &lt;span class="s2"&gt;"Summarize the current instructions."&lt;/span&gt;
codex &lt;span class="nt"&gt;--cd&lt;/span&gt; packages/api &lt;span class="nt"&gt;--ask-for-approval&lt;/span&gt; never &lt;span class="s2"&gt;"Show which instruction files are active."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a record, start Codex with &lt;code&gt;codex -c log_dir=./.codex-log&lt;/code&gt; and read &lt;code&gt;./.codex-log/codex-tui.log&lt;/code&gt;. Codex rebuilds the chain on every run, so restart it in the target folder after an edit.&lt;/p&gt;

&lt;p&gt;In Claude Code, an interactive session with no &lt;code&gt;CLAUDE.md&lt;/code&gt; on the path shows a line starting &lt;code&gt;no CLAUDE.md found; AGENTS.md loaded:&lt;/code&gt; followed by the file's path. Run &lt;code&gt;/memory&lt;/code&gt; and look for that path, or run &lt;code&gt;/context all&lt;/code&gt; and check the list under Memory files. Before v2.1.280 neither one listed an &lt;code&gt;AGENTS.md&lt;/code&gt; read directly, so on older versions ask Claude Code what its project instructions say. A nested &lt;code&gt;CLAUDE.md&lt;/code&gt; that loads on demand prints a &lt;code&gt;Loaded&lt;/code&gt; line with its path when it enters the session. If &lt;code&gt;/config&lt;/code&gt; has no Project instructions setting, your session can't read &lt;code&gt;AGENTS.md&lt;/code&gt; directly, and a &lt;code&gt;CLAUDE.md&lt;/code&gt; with &lt;code&gt;@AGENTS.md&lt;/code&gt; is the way in.&lt;/p&gt;

&lt;p&gt;The checklist for one package:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start in the package folder and run the script.&lt;/li&gt;
&lt;li&gt;Remove or rename any &lt;code&gt;AGENTS.override.md&lt;/code&gt; you didn't mean to keep.&lt;/li&gt;
&lt;li&gt;Remove stray &lt;code&gt;CLAUDE.md&lt;/code&gt;, &lt;code&gt;.claude/CLAUDE.md&lt;/code&gt; and &lt;code&gt;CLAUDE.local.md&lt;/code&gt; files on the path, or set Project instructions to &lt;code&gt;claude-md-and-agents-md&lt;/code&gt; in &lt;code&gt;/config&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Keep the Codex combined size under 32 KiB, or raise &lt;code&gt;project_doc_max_bytes&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Run both Codex commands above and confirm the package file comes last.&lt;/li&gt;
&lt;li&gt;In Claude Code, look for the &lt;code&gt;AGENTS.md loaded&lt;/code&gt; line or the path in &lt;code&gt;/memory&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the script turns up a file you didn't write, my post on the &lt;a href="https://rayastats.com/en/blog/agents-md-claude-md-downloads-folder" rel="noopener noreferrer"&gt;planted AGENTS.md in Downloads&lt;/a&gt; has commands to check where it came from and what it changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  One global rulebook for Claude Code and Codex
&lt;/h2&gt;

&lt;p&gt;The global layer sits at the top of every chain above, so I keep mine in one place. In the &lt;a href="https://rayastats.com/en/two-engines-kit" rel="noopener noreferrer"&gt;Two Engines Kit&lt;/a&gt;, &lt;code&gt;RULES.md&lt;/code&gt; holds the rules Claude Code and Codex share, and &lt;code&gt;claude.md&lt;/code&gt; and &lt;code&gt;codex.md&lt;/code&gt; hold the few lines that differ. The installer points &lt;code&gt;~/.claude/CLAUDE.md&lt;/code&gt; at the shared rules and builds &lt;code&gt;~/.codex/AGENTS.md&lt;/code&gt; from them with &lt;code&gt;sync.sh&lt;/code&gt;. It previews each change, asks first and backs up what it replaces, and &lt;code&gt;uninstall.sh&lt;/code&gt; restores it. The kit is built on information gathered since the first Codex and Claude CLIs.&lt;/p&gt;

&lt;p&gt;Project and package &lt;code&gt;AGENTS.md&lt;/code&gt; files stay in your repo, so every check in this post applies with the kit installed too. If you run Claude Code and Codex on one machine, get the Two Engines Kit at &lt;a href="https://rayastats.com/en/two-engines-kit" rel="noopener noreferrer"&gt;https://rayastats.com/en/two-engines-kit&lt;/a&gt; and keep one global rulebook for both.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claudecode</category>
      <category>codex</category>
      <category>monorepo</category>
    </item>
    <item>
      <title>Shared API key or per-user OAuth: how to put an MCP server over your existing API</title>
      <dc:creator>Iliya Oblakov</dc:creator>
      <pubDate>Mon, 09 Nov 2026 19:56:43 +0000</pubDate>
      <link>https://dev.to/iliyaoblakov/shared-api-key-or-per-user-oauth-how-to-put-an-mcp-server-over-your-existing-api-fj8</link>
      <guid>https://dev.to/iliyaoblakov/shared-api-key-or-per-user-oauth-how-to-put-an-mcp-server-over-your-existing-api-fj8</guid>
      <description>&lt;p&gt;On 5 November 2026, &lt;a href="https://stackoverflow.com/questions/79808966" rel="noopener noreferrer"&gt;a Stack Overflow question&lt;/a&gt; asking whether Claude Desktop can open an authorization URL and accept a token without OAuth 2.0/PKCE had 749 views. &lt;a href="https://stackoverflow.com/questions/79619486" rel="noopener noreferrer"&gt;Another&lt;/a&gt;, on how to pass an API key to tool calls in a Cloudflare remote MCP server, had 605. Authentication is where builders get stuck first when they put an MCP server over an existing API.&lt;/p&gt;

&lt;p&gt;I checked the MCP specification and the Claude and ChatGPT connector docs the same day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your MCP server handles two credentials
&lt;/h2&gt;

&lt;p&gt;The client presents one to your MCP server. Your MCP server presents the other to your API. Keep them separate.&lt;/p&gt;

&lt;p&gt;Your API keeps its authentication, permissions and business logic, and the MCP server enforces them on every call. The MCP server can hold its own API key for those calls. If each user needs their own API permissions, give them a separate grant with your API instead of forwarding their MCP access token.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the client authenticates to your MCP server
&lt;/h2&gt;

&lt;p&gt;Claude and ChatGPT connectors reach a remote server over Streamable HTTP, one of the two transports in the &lt;a href="https://modelcontextprotocol.io/specification/latest/basic/transports" rel="noopener noreferrer"&gt;MCP transports specification&lt;/a&gt;. A stdio server, which the client launches as a subprocess, fits local development and takes its credentials from the environment.&lt;/p&gt;

&lt;p&gt;Authorization is optional in MCP. When you protect an HTTP server, the &lt;a href="https://modelcontextprotocol.io/specification/latest/basic/authorization" rel="noopener noreferrer"&gt;MCP authorization specification&lt;/a&gt; uses OAuth 2.1, and your MCP server acts as a resource server:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It answers an unauthenticated request with &lt;code&gt;401&lt;/code&gt; and a &lt;code&gt;WWW-Authenticate&lt;/code&gt; header pointing at its Protected Resource Metadata document.&lt;/li&gt;
&lt;li&gt;The client discovers the authorization server from that document.&lt;/li&gt;
&lt;li&gt;Your server validates that each token was issued for it, and never accepts or forwards a token issued for another resource.&lt;/li&gt;
&lt;li&gt;Tokens never go in the URL query string.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For a shared credential, Claude and ChatGPT each document a static-key path:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Claude supports &lt;code&gt;static_headers&lt;/code&gt;. An organization Owner enters an API key or bearer token when adding the connector, and Claude sends it in a request header on every call. &lt;a href="https://claude.com/docs/connectors/building/authentication" rel="noopener noreferrer"&gt;Anthropic's authentication page&lt;/a&gt; marks it beta and limits it to a set of organizations. Standard header names such as &lt;code&gt;authorization&lt;/code&gt;, &lt;code&gt;x-api-key&lt;/code&gt; and &lt;code&gt;x-auth-token&lt;/code&gt; work, and other names need Anthropic's approval.&lt;/li&gt;
&lt;li&gt;ChatGPT plugins declare API key authentication in &lt;code&gt;mcp.json&lt;/code&gt; under &lt;code&gt;extensions["com.openai"].auth&lt;/code&gt;, with &lt;code&gt;headerScheme&lt;/code&gt; set to &lt;code&gt;bearer&lt;/code&gt;, &lt;code&gt;basic&lt;/code&gt; or &lt;code&gt;custom_header&lt;/code&gt;. &lt;a href="https://developers.openai.com/plugins/build/plugins" rel="noopener noreferrer"&gt;OpenAI's packaging reference&lt;/a&gt; notes that the submission portal's connection form doesn't support these declarations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the first Stack Overflow question, the static-key route in Anthropic's docs is &lt;code&gt;static_headers&lt;/code&gt;, with the limits above, and a shared key can't identify an individual user. For the second, both static-key paths carry the key in a request header. Keep it out of tool arguments, because a model that can see a key in a parameter can repeat it or send it somewhere else.&lt;/p&gt;

&lt;p&gt;Choose OAuth when a tool reads one user's data or acts in their name. A static key fits a service account where everyone shares the same permissions. For a static key, document the header name and key source, and keep keys out of URLs and logs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Map operations to focused tools
&lt;/h2&gt;

&lt;p&gt;Start with a few read operations and one workflow you can test end to end, then add writes once the reads hold up.&lt;/p&gt;

&lt;p&gt;Give each user goal its own tool, such as &lt;code&gt;list_invoices&lt;/code&gt;, &lt;code&gt;get_invoice&lt;/code&gt; and &lt;code&gt;create_invoice&lt;/code&gt;. One generic &lt;code&gt;api_request&lt;/code&gt; tool with a &lt;code&gt;method&lt;/code&gt; parameter gives the model no useful guidance and fails directory review. &lt;a href="https://claude.com/docs/connectors/building/review-criteria" rel="noopener noreferrer"&gt;Anthropic's connector review criteria&lt;/a&gt; require separate read and write tools and recommend splitting create, update and delete.&lt;/p&gt;

&lt;p&gt;Each tool needs a name, a description that says when to use it, an explicit JSON Schema for its input (&lt;code&gt;inputSchema&lt;/code&gt; in the &lt;a href="https://modelcontextprotocol.io/specification/latest/server/tools" rel="noopener noreferrer"&gt;MCP tools specification&lt;/a&gt;), and annotations that match its behavior. Annotations guide client confirmations but don't replace your own authorization.&lt;/p&gt;

&lt;p&gt;For the Claude directory, the same criteria require a &lt;code&gt;title&lt;/code&gt;, accurate &lt;code&gt;readOnlyHint&lt;/code&gt; and &lt;code&gt;destructiveHint&lt;/code&gt; annotations, and names of 64 characters or fewer. Descriptions have to match what the tool does, and they can't contain behavioral instructions or pull instructions from external sources.&lt;/p&gt;

&lt;p&gt;Return the fields the next step needs, cap lists, and pass a continuation value for the next page. Anthropic's &lt;a href="https://claude.com/docs/connectors/building" rel="noopener noreferrer"&gt;connector build guide&lt;/a&gt; caps a tool result at about 150,000 characters on claude.ai and Desktop, and at 25,000 tokens by default in Claude Code. OpenAI's &lt;a href="https://developers.openai.com/plugins/build/mcp-server" rel="noopener noreferrer"&gt;build guide&lt;/a&gt; says to keep secrets and unnecessary personal data out of results.&lt;/p&gt;

&lt;p&gt;For a workflow that spans calls, like a draft order, return an explicit handle from the creation tool and accept it on later calls. Check on every call that the handle belongs to the caller.&lt;/p&gt;

&lt;h2&gt;
  
  
  Write errors the model can act on
&lt;/h2&gt;

&lt;p&gt;Use one input object for path, query and body fields, enums for fixed values, and formats in the parameter descriptions. If the caller can say "Acme" instead of a UUID, resolve the name inside the tool.&lt;/p&gt;

&lt;p&gt;Return &lt;code&gt;structuredContent&lt;/code&gt; for later tool calls and a text &lt;code&gt;content&lt;/code&gt; block that says what happened. Stable IDs let the next call refer to the same record.&lt;/p&gt;

&lt;p&gt;Return execution errors with &lt;code&gt;isError: true&lt;/code&gt; and text the model can act on. Turn an upstream 404 into a message like this one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;No invoice with ID 123. Call list_invoices to find a valid ID.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Give validation failures the expected format and rate limits a retry delay. Don't pass through stack traces or raw upstream bodies.&lt;/p&gt;

&lt;p&gt;For rules that span tools, OpenAI supports server-wide &lt;code&gt;instructions&lt;/code&gt; and advises putting the most important material in the first 512 characters. ChatGPT uses &lt;code&gt;readOnlyHint&lt;/code&gt; to decide which calls need confirmation, and it treats any tool without it as a write action.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test in the Inspector, then in Claude and ChatGPT
&lt;/h2&gt;

&lt;p&gt;Run the MCP Inspector first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @modelcontextprotocol/inspector
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check initialization, the tool list, every tool with valid and invalid inputs, and the errors that come back. Then connect both clients:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In Claude, add the server as a custom connector by URL and run through the auth flow.&lt;/li&gt;
&lt;li&gt;In ChatGPT, go to Plugins, choose Add custom MCP server, enter the HTTPS &lt;code&gt;/mcp&lt;/code&gt; URL and set up authentication. OpenAI's &lt;a href="https://developers.openai.com/plugins/build/app-quickstart" rel="noopener noreferrer"&gt;quickstart&lt;/a&gt; includes a tunnel for local development.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Test prompts that should pick each tool, prompts that should pick none, wrong IDs, expired credentials and a write that needs confirmation. Refresh the connection after you change tool metadata.&lt;/p&gt;

&lt;h2&gt;
  
  
  Plan a review round for Claude's directory
&lt;/h2&gt;

&lt;p&gt;Anthropic's criteria require testing every tool through the Inspector and as a custom connector before you submit. The review also requires a populated test account, public documentation by your publish date, actionable error messages, and first-party APIs or APIs you legitimately proxy.&lt;/p&gt;

&lt;p&gt;If your connector already exists, my &lt;a href="https://rayastats.com/en/ai-connector-review" rel="noopener noreferrer"&gt;AI connector review&lt;/a&gt; checks it before you submit. It's an independent review of an existing MCP connector against Anthropic's connector pre-submission checklist, and every finding comes with reproduction steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to prepare before an MCP build
&lt;/h2&gt;

&lt;p&gt;Bring these to the first conversation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Your OpenAPI document, or a list of the operations with their request and response shapes.&lt;/li&gt;
&lt;li&gt;Three to five user requests the server should handle, written the way a user would type them.&lt;/li&gt;
&lt;li&gt;The operations to leave out, including admin, irreversible and billing actions.&lt;/li&gt;
&lt;li&gt;The auth decision: shared API key or per-user OAuth, and who issues each credential.&lt;/li&gt;
&lt;li&gt;A test account with realistic data and a way to reset it.&lt;/li&gt;
&lt;li&gt;The deployment target for an HTTPS endpoint and where its secrets live.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I build MCP servers over existing APIs for Claude and ChatGPT. The scope starts from that list: the tool map, the auth path, the error behavior and a tested connection to the client you use. If you want me to build yours, start on the &lt;a href="https://rayastats.com/en/ai-connector-build" rel="noopener noreferrer"&gt;AI connector build page&lt;/a&gt; with that list ready.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>api</category>
      <category>oauth</category>
    </item>
    <item>
      <title>Four unreviewed commits, one already merged: how my Claude Code and Codex setup got its rules</title>
      <dc:creator>Iliya Oblakov</dc:creator>
      <pubDate>Mon, 09 Nov 2026 18:16:04 +0000</pubDate>
      <link>https://dev.to/iliyaoblakov/four-unreviewed-commits-one-already-merged-how-my-claude-code-and-codex-setup-got-its-rules-k15</link>
      <guid>https://dev.to/iliyaoblakov/four-unreviewed-commits-one-already-merged-how-my-claude-code-and-codex-setup-got-its-rules-k15</guid>
      <description>&lt;p&gt;One night in October I found four test-only commits in my own projects that no reviewer had ever read. One was already merged. The other three were waiting in the merge queue.&lt;/p&gt;

&lt;p&gt;Nothing broke. Codex confirmed all four before the queue resumed. That night is the reason the rules in my setup now live in scripts instead of in my head.&lt;/p&gt;

&lt;p&gt;This is how the setup came together, one failure at a time, and what you can copy from it today.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why one engine can't sign off its own work
&lt;/h2&gt;

&lt;p&gt;The session that wrote a change is the worst place to decide whether that change is finished. It already believes it is.&lt;/p&gt;

&lt;p&gt;So I split the jobs. Claude Code writes the code, and Codex reviews it read-only. I picked a reviewer from a different company because it has different blind spots. On one pull request in October the first Codex review came back with 2 blockers and 8 should-fix findings. That single review is why the loop exists.&lt;/p&gt;

&lt;p&gt;The reviewer has to be read-only. A reviewer that can fix what it finds is a second author, and nobody reviewed that second author. Codex runs in a read-only sandbox, so every fix, small ones too, goes back to Claude Code as a fail list, and the new commit gets reviewed again.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a second engine catches
&lt;/h2&gt;

&lt;p&gt;Four findings from Codex reviews of my own pull requests, with project details removed. Each came with the file, the line and a way to make it fail.&lt;/p&gt;

&lt;p&gt;1) A CI step turned screenshot capture errors into report rows but never counted them as problems. A timeout could finish with every report written, zero problems and a green build.&lt;br&gt;
2) A guard meant to keep a publishing script away from the wrong database read only one shell variable. A target chosen through the environment file got past it.&lt;br&gt;
3) A new exemption for media links ran before the scheme and host checks, so links to a local dev server and ftp addresses passed an audit built to reject them.&lt;br&gt;
4) A stricter validation step crashed on one malformed field in an API response that the old code had reported cleanly, which would have stopped the rest of the audit run.&lt;/p&gt;

&lt;p&gt;The first two were blockers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The night the review became a file
&lt;/h2&gt;

&lt;p&gt;Since that night, the review script saves the Codex verdict in a file named after the exact commit it reviewed. A new commit has no verdict until it's reviewed, so an ACCEPT from an earlier commit can't cover code nobody read. With &lt;code&gt;REQUIRE_REVIEW=1&lt;/code&gt; the merge script refuses any commit without its own ACCEPT file.&lt;/p&gt;

&lt;p&gt;The script is strict about what counts as a verdict. It reads only the reviewer's final message, and it accepts the result only when Codex exited 0, the checkout didn't change during the review and the final message has a single &lt;code&gt;Verdict:&lt;/code&gt; line. A crash, a cut-off answer and a checkout that moved mid-review all end with exit 1 and no verdict file. Two &lt;code&gt;Verdict:&lt;/code&gt; lines count as none, because the script has no way to know which one the reviewer meant.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bill that moved CI onto my machine
&lt;/h2&gt;

&lt;p&gt;One private repository spent about 2,100 GitHub Actions minutes in its first five days of hosted CI, on a free tier of 2,000 minutes a month. Since then every check runs on my machine first, and work is pushed only after it passes there.&lt;/p&gt;

&lt;p&gt;The local gate runs your tests on the same commit Codex reviewed. It writes a pass record only after a full pass on a tree that was clean before and after the run. It refuses a dirty tree because a check could pass on a file that isn't in the commit. The merge script refuses to push without that record for the exact commit, then merges with GitHub's &lt;code&gt;sha&lt;/code&gt; parameter, so GitHub refuses the merge if the branch head moved in between.&lt;/p&gt;

&lt;p&gt;My own gate runs 40 to 43 checks. Run one group after another, a full pass took 35 to 40 minutes. Split into five parallel lanes, it takes about 11 minutes on that machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  The test that changed which model does what
&lt;/h2&gt;

&lt;p&gt;On 12 October I gave both engines the same task: a Tetris implementation against 44 hidden tests, three runs each. Claude Code with Opus 5.5 at xhigh and Codex with GPT-6.1-Sol at xhigh both passed 44/44 in all three runs. The median times were 21:17 and 18:13. Opus cost 8.9 times as much.&lt;/p&gt;

&lt;p&gt;So work goes by what a mistake costs. Bounded implementation with a clear brief goes to Sonnet. Haiku gets search, extraction and listing, where a wrong answer is cheap to spot. xhigh and max effort go to design-heavy slices, the first review of a pull request and hard debugging. The Tetris run is one task, so those last rows keep xhigh until they get their own rerun.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the setup can't do
&lt;/h2&gt;

&lt;p&gt;These checks cannot make the code correct. If Claude Code weakens a test in the same commit that breaks the code, the test gate passes that commit, and catching it falls to the reviewer. The review prompt says new tests must fail on the old behaviour and nothing gets weakened without a stated reason, and Codex checks the diff against that rule. A review can miss things.&lt;/p&gt;

&lt;p&gt;A wrong ACCEPT looks the same as a right one. That's why ACCEPT only unblocks the pull request in my setup, and I do the merge myself.&lt;/p&gt;

&lt;p&gt;The recorded demo of the kit 1.0.2 scripts on 23 October shows the refusing side. It ran on a throwaway project with real Codex reviews and DRY_RUN merges, and the merge script refused all seven attempts because the gate log was missing, the gate had not passed, or the verdict was missing or not ACCEPT. The demo never reached ACCEPT and nothing was merged.&lt;/p&gt;

&lt;h2&gt;
  
  
  Copy this into your own repo
&lt;/h2&gt;

&lt;p&gt;1) Keep one rules file both engines load. Claude Code imports it into &lt;code&gt;CLAUDE.md&lt;/code&gt; with an &lt;code&gt;@&lt;/code&gt; line, and a small script builds &lt;code&gt;AGENTS.md&lt;/code&gt; for Codex from the same source.&lt;br&gt;
2) Run Claude Code in its own git worktree, away from your main branch.&lt;br&gt;
3) Run Codex with a read-only sandbox, and make its final message carry a single verdict line, &lt;code&gt;Verdict: ACCEPT&lt;/code&gt; or &lt;code&gt;Verdict: CHANGES REQUIRED&lt;/code&gt;.&lt;br&gt;
4) Save that verdict in a file named after the commit hash.&lt;br&gt;
5) Run your tests on the same commit, on a clean tree, and record a pass only after a full pass.&lt;br&gt;
6) Let a script refuse the merge when the current commit lacks either file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get the full loop
&lt;/h2&gt;

&lt;p&gt;The Two Engines Kit is this loop packed so one script installs it. You get one rulebook both engines load, the cross-review and gate scripts, a merge script that refuses without a pass record for the exact commit (and, with &lt;code&gt;REQUIRE_REVIEW=1&lt;/code&gt;, without the saved ACCEPT), and GUIDE.md, which explains each piece and the mistake that produced it. The installer previews every change and asks y/N, and &lt;code&gt;uninstall.sh&lt;/code&gt; puts your old files back byte for byte. It needs both Claude Code and Codex, supports WSL2, Linux and macOS, and is tested on WSL2 with Ubuntu 24.04.&lt;/p&gt;

&lt;p&gt;I've been building it since the first Codex and Claude CLIs. Get the Two Engines Kit at &lt;a href="https://rayastats.com/en/two-engines-kit" rel="noopener noreferrer"&gt;https://rayastats.com/en/two-engines-kit&lt;/a&gt;&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>codex</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>A 7-point checklist before you let Codex review Claude Code commits</title>
      <dc:creator>Iliya Oblakov</dc:creator>
      <pubDate>Thu, 05 Nov 2026 14:17:24 +0000</pubDate>
      <link>https://dev.to/iliyaoblakov/a-7-point-checklist-before-you-let-codex-review-claude-code-commits-3mk7</link>
      <guid>https://dev.to/iliyaoblakov/a-7-point-checklist-before-you-let-codex-review-claude-code-commits-3mk7</guid>
      <description>&lt;p&gt;Claude Code writes, Codex reviews, a gate decides what merges. Here's the short version I check before trusting that loop on a repo. The full walkthrough, with the prompts, is in &lt;a href="https://rayastats.com/en/blog/how-to-make-codex-review-claude-code-commits" rel="noopener noreferrer"&gt;How to make Codex review Claude Code's commits&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Codex runs read-only.&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;codex exec -s read-only --color never --output-last-message &amp;lt;file&amp;gt; -&lt;/code&gt;, with the prompt on stdin. The reviewer can read files and run &lt;code&gt;git diff&lt;/code&gt;, but it can't write or hand back a patch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. One verdict line, saved under the commit hash.&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;Verdict: ACCEPT&lt;/code&gt; or &lt;code&gt;Verdict: CHANGES REQUIRED&lt;/code&gt;, read only from the &lt;code&gt;--output-last-message&lt;/code&gt; file. A crash, an empty answer or two Verdict lines leave no saved verdict, so the commit can't merge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. A brief exists before the code.&lt;/strong&gt;&lt;br&gt;
Every line under "Done means" is something a reviewer can confirm from the diff or a test. Write "a customer email with a comma is escaped", never "export works well".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Each new test failed on the base commit.&lt;/strong&gt;&lt;br&gt;
The engine that writes the code often writes the tests too. A test that already passes on the old code proves nothing about the change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. House rules live where both engines read them.&lt;/strong&gt;&lt;br&gt;
CLAUDE.md for Claude Code, AGENTS.md for Codex. Keep the AGENTS.md part short: Codex stops adding instruction files once their combined size reaches &lt;code&gt;project_doc_max_bytes&lt;/code&gt;, 32 KiB by default.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. The reviewer answers every line.&lt;/strong&gt;&lt;br&gt;
Met with a file:line, not met with what's missing, or not checkable with the evidence it would need. Findings outside the brief need a concrete failure. Style preferences aren't findings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Rounds have a hard cap.&lt;/strong&gt;&lt;br&gt;
Later rounds raise only what blocks the merge. If the last allowed round says CHANGES REQUIRED, the merge stays blocked and a person decides.&lt;/p&gt;

&lt;p&gt;The merge is refused unless that exact commit has a test pass and a saved ACCEPT. A test pass alone won't catch a test Claude Code weakened in the same commit, so that falls to the reviewer: &lt;a href="https://rayastats.com/en/blog/review-gate-weakened-deleted-tests" rel="noopener noreferrer"&gt;what my review gate does with weakened and deleted tests&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you want the whole setup prebuilt, the &lt;a href="https://rayastats.com/en/two-engines-kit" rel="noopener noreferrer"&gt;Two Engines Kit&lt;/a&gt; for Claude Code and Codex is a paid kit I made, built on information gathered since the first Codex and Claude CLIs.&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>codex</category>
      <category>codereview</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
