{"slug": "a-hermes-agent-skill-looping-between-codex-and-claude-code", "title": "A Hermes Agent Skill Looping Between Codex and Claude Code", "summary": "Tadashi Shigeoka released omh-issue-loop, a Hermes Agent skill that automates the full journey from a GitHub issue to a pull request by orchestrating Codex CLI for implementation and Claude Code CLI for review, with the orchestrator prohibited from touching code. The skill, added to the oh-my-hermes collection via PR #6, does not merge or close the issue, and Shigeoka reports that running it daily shifted his own time to other tasks.", "body_md": "# Hand It an Issue, Get Back a PR — A Hermes Agent Skill Looping Between Codex and Claude Code\n\n[Tadashi Shigeoka](/en/author/tadashi-shigeoka/)· Thu, July 23, 2026\n\nLast time I wrote `omh-pr-multi-review`\n\n, a skill that has [Claude Code](https://code.claude.com/docs/en/overview) and [Codex](https://openai.com/codex/) independently review the same pull request ([Reviewing a PR with Claude Code and Codex at Once](/en/posts/multi-model-pr-review-claude-code-codex-hermes-agent-skill/)). That automated a step that happens after a PR already exists.\n\nThis time I went upstream and automated everything from the issue to the PR. I added `omh-issue-loop`\n\nto [oh-my-hermes](https://github.com/codenote-net/oh-my-hermes), my collection of customizations for [Hermes Agent](https://github.com/NousResearch/hermes-agent) ([PR #6](https://github.com/codenote-net/oh-my-hermes/pull/6)).\n\nUsage is one URL: a GitHub issue. From there the agent implements, validates, reviews, fixes the findings, verifies again, and stops with a pull request a human can review. It does not merge. It does not close the issue.\n\nI run this skill in my day-to-day development. What running it revealed is that my own time has moved somewhere else entirely. The second half of this post is about that.\n\nI have kept running my daily work through this skill since first publishing it, closing the holes that surfaced as they showed up. This post reflects the current state. What running it for real taught me turned out to be more interesting than what I had reasoned out at design time, so that is in here too.\n\n## What the skill contains\n\nThat is the whole of it.\n\nThe previous skill, `omh-pr-multi-review`\n\n, shipped a Python script so the control flow lived in deterministic code. This one has no script at all: a SKILL.md describing the procedure in natural language, plus the two references it points to.\n\nThat is not a reversal, it reflects a different problem shape. Multi-model review was a branchless, side-effect-heavy straight line: switch checkout, invoke four CLIs in order, always restore. A deterministic script is the natural fit.\n\nAn issue implementation loop is not that. Validation commands differ per repository, what to do next depends on what the reviewers said, and whether it converges at all is unknown in advance. Scripting that means either hardcoding repository-specific assumptions or demanding an enormous config file. So I left the judgment with the agent and instead wrote the prohibitions and the limits precisely.\n\n## The orchestrator never implements\n\nSKILL.md opens not with what to do but with what not to do.\n\nOrchestrate the workflow only. Delegate working-tree implementation and fixes to Codex CLI and all independent review and behavior verification to Claude Code CLI. Keep every Git history, remote, pull-request, issue, CI, and review operation under exclusive orchestrator control. Never implement, fix, review, merge, close the issue, or widen scope in the orchestrating agent.\n\nThat separation is the core of the design, for two reasons.\n\nFirst, having the author review its own work is worthless. When the same model in the same session reviews its own diff, it just ratifies its earlier judgment. Only by splitting lineages (Codex writes, Claude Code reviews) does the review become independent information.\n\nSecond, the moment the orchestrator starts touching code, state becomes untraceable. Allow “Codex almost got it, I will just fix this bit myself” and the record of who wrote what disappears. The evidence the PR eventually carries (the changes Codex authored, finding counts per reviewer) collapses right there.\n\n## The worker never orchestrates either\n\nThe third sentence of the quote above, the one reserving every Git, remote, pull-request, issue, CI, and review operation to the orchestrator, was added after publication. Running the skill showed that a one-sided separation does not hold.\n\nHand Codex a bare `/goal <issue-url>`\n\nand it reads that as an end-to-end delivery task. Reading the issue and implementing it is exactly what I wanted. Then it commits, pushes, opens a pull request, marks it ready, and watches CI to completion, all before a single one of the orchestrator’s review gates has run.\n\nThat is not Codex misbehaving. Given an issue and a `/goal`\n\n, reading the objective as “get this to a pull request” is a perfectly reasonable interpretation. I simply had not written down where its job ended.\n\nSo the responsibility boundary became its own section.\n\nTreat Codex implementation and fix processes as working-tree workers, not autonomous issue owners.\n\nA worker may read the issue snapshot handed to it by the parent and the repository instructions, edit only issue-scoped files, run the relevant local validation, and report. That is the whole list. All changes are left uncommitted, and no worker retrieves live issue data.\n\nCommits, pushes, pull-request creation, edits and readying, PR check inspection, issue updates, review execution, and final reporting are all reserved to the orchestrator. And this applies not only to the initial implementation but to every subsequent fix invocation.\n\nNever rely on the worker to infer the boundary from the surrounding workflow.\n\nThat was the real lesson. However carefully the orchestrator’s SKILL.md states that reviews belong to the orchestrator, the Codex process launched separately never reads it. Anything not written into the prompt handed to the worker does not exist as far as the worker is concerned.\n\nThe prompt actually passed is nothing clever, just the prohibitions spelled out.\n\nThe report format is pinned too: files changed, validation commands executed, exit status and result for each, and any blockers. Those four items and nothing else. Leave it free-form and the worker writes its session as a narrative, and you can no longer tell what was actually executed.\n\n## The issue is read exactly once\n\nIn the prompt above, the `ISSUE SNAPSHOT`\n\nblock sits ahead of the prohibitions.\n\nThe orchestrator is the only process allowed to fetch the issue, and it does so exactly once, during preflight.\n\nThat output is preserved verbatim as an immutable snapshot and never refreshed for the rest of the run: not after implementation, not during side-effect checks, not inside review loops, not before final reporting. Title, body, acceptance criteria, labels, and number are derived from that snapshot alone.\n\nThe whole block is then embedded in every child prompt handed to Codex and Claude Code. The implementation worker, every fix worker, the local reviews, the PR review, and the fresh-worktree verifier all get the same bytes, along with an explicit statement that the snapshot is authoritative and that `gh issue view`\n\nor `gh api`\n\nagainst the issue is forbidden.\n\nPassing only the issue URL is insufficient.\n\nThat is the crux. Give a child only the URL and it will naturally go fetch the issue itself. Once it can, an issue edited mid-run means reviewers judging the same diff against different specifications. Pinning every gate to one SHA buys nothing if the spec itself is drifting underneath.\n\nFor the same reason, URL-based slash-command syntax that makes a child fetch the issue implicitly is banned as well. The URL stays in prompts purely as provenance. If the single preflight fetch fails or returns incomplete JSON, the run stops there rather than delegating retrieval to a child.\n\n## A prohibition only becomes one once you verify it\n\nWriting a prohibition into a prompt and having it observed are two different things.\n\nEvery other dangerous path in this skill could be closed by not implementing it. Stash and force push never happen if the procedure does not mention them. But the worker is a separate process running a different model, so there is no path to close. Whether the prohibitions held can only be observed after the fact.\n\nSo the skill now snapshots state before and after every worker process and compares. Before, it captures:\n\n- the current HEAD SHA and\n`git log --oneline <base-sha>..HEAD`\n\n- the current branch name and that branch’s reflog\n`git status --short --branch`\n\nand the tracked, staged, and untracked file sets- the remote branch OID from\n`git ls-remote --heads origin <branch-name>`\n\n, recording absence as a value - every PR for the branch from\n`gh pr list --repo <owner>/<repo> --head <branch-name> --state all`\n\n, plus enough`gh pr view`\n\nmetadata to detect edits, readiness, state, body, title, and head changes - the immutable issue snapshot captured during preflight, never re-fetched for the baseline\n\nThe reflog is the quietly important one. If the worker commits and then resets back, comparing HEAD alone shows nothing happened. The reflog still carries the evidence.\n\nOnce the worker exits, a fail-closed comparison runs before any review at all: exit status zero, all four report sections present, HEAD and the commit range unchanged, no commit, amend, or reset in the reflog, the remote branch OID unmoved, and the branch PR snapshot matching its baseline.\n\nOn top of that, prohibited commands found in the captured worker output fail the check even when observable state did not change. An attempted `git push`\n\nthat failed leaves the remote untouched, but the boundary was still crossed. Any attempt to fetch or mutate the issue is treated the same way: it fails the check even though the remote issue never moved.\n\nIf any check fails, or cannot be completed, the run stops immediately.\n\nDo not undo or conceal the worker action.\n\nThat sentence is deliberate. Allow “there was a stray commit so I reset it and continued” and boundary violations become routine, and unrecorded. Instead the run reports an orchestration failure with the before/after evidence. No local reviews, no PR creation or update, no further worker invocation.\n\nTreating a check that could not be completed as a failure is exactly the same principle applied to reviews: never treat “could not determine” as a pass. That is the single rule running through this entire skill, and this change extended it to the Git and GitHub side.\n\n## Fixed execution settings\n\nThe delegation settings are hardcoded in SKILL.md.\n\nRunning the implementing Codex at `model_reasoning_effort=\"low\"`\n\nand `service_tier=\"fast\"`\n\nwhile the reviewing Claude Code gets `--effort high`\n\nis deliberate.\n\nGiven that a loop exists, implementation should be fast. Somewhat sloppy output is fine because five downstream gates will catch it. If the review side is sloppy, however, sloppy implementations sail through and the entire loop stops meaning anything. Heavy reasoning belongs on the side that judges, not the side that writes.\n\nPinning the permission mode turned out to be mandatory in practice: `--yolo`\n\nfor Codex, `--permission-mode auto`\n\nfor Claude Code. Children are launched non-interactively, so the instant a permission prompt appears there is nobody to answer it and the process just sits there. From the orchestrator that is indistinguishable from a hang, and you end up in the same “stalled but plausible-looking” state as a timed-out review. In an unattended loop, permission decisions have to be settled at launch time.\n\nWhat that permissiveness buys is bounded on the other side, by the prohibitions in the prompt and the post-execution side-effect check. The worker runs with `--yolo`\n\nand is still forbidden to commit, push, or touch a pull request, and the before/after snapshot comparison confirms it did not. Loosening the permission mode and widening what the worker is allowed to do are kept as separate questions.\n\nAs with the previous skill, Codex settings are injected per process via `-c`\n\nand the global `config.toml`\n\nis never touched. A tool invoked by an agent that mutates the user’s environment is an incident waiting to happen.\n\n## The overall flow\n\nBefore the procedure itself, SKILL.md sends the agent to `references/workflow-flowchart.md`\n\nfirst. It renders the phases, ownership boundaries, loops, stop paths, CI gate, and human handoffs as a single flowchart, and states explicitly that SKILL.md remains authoritative wherever details differ. Reading only the procedure top to bottom makes it easy for an agent to lose track of which loop it is currently in, which is something running it made obvious.\n\nRearranged for this post, the whole thing looks like this.\n\n``` php\nflowchart TD\n    IN[Receive issue URL] --> PRE[Preflight: CLIs, auth,<br/>repository match, clean tree,<br/>discover validation and signoff]\n    PRE -->|Fail| STOP[Stop and ask the user]\n    PRE -->|Pass| SNAP[Run gh issue view exactly once<br/>Save the immutable snapshot]\n    SNAP --> BR[Branch omh/issue-N-topic<br/>from default branch tip]\n    BR --> BASE[Capture side-effect baseline<br/>HEAD, reflog, remote, PRs]\n    BASE --> IMPL[Codex implements under the<br/>restricted prompt, uncommitted]\n    IMPL --> CHK{Side-effect check<br/>fail-closed}\n    CHK -->|Violation| ABORT[Stop, report before/after<br/>never undo or conceal]\n    CHK -->|Clean| L1[Codex /review]\n    CHK -->|Clean| L2[Claude /code-review]\n    CHK -->|Clean| L3[Claude /security-review]\n    L1 --> GATE1{Zero high-priority?}\n    L2 --> GATE1\n    L3 --> GATE1\n    GATE1 -->|No| LIM{Shared fix count<br/>below 10?}\n    LIM -->|Yes| FIX[Codex fixes high-priority only]\n    FIX --> BASE\n    LIM -->|No| HAND[Stop: at-mention progress<br/>on the PR, or the issue]\n    HAND --> HM1[Human decides how to continue]\n    GATE1 -->|Yes| SIGN[Signing preflight, commit, push]\n    SIGN --> SO{signoff_required?}\n    SO -->|Yes| SOOK[Sign off and verify<br/>status for that SHA]\n    SOOK --> PR\n    SO -->|No| PR[Run full validation, open draft PR]\n    PR --> P1[Codex /review]\n    PR --> P2[Claude /code-review]\n    PR --> P3[Claude /security-review]\n    PR --> P4[Claude /review #N]\n    PR --> P5[Fresh-worktree behavior verification]\n    P1 --> GATE2{All five sources,<br/>same SHA, zero high?}\n    P2 --> GATE2\n    P3 --> GATE2\n    P4 --> GATE2\n    P5 --> GATE2\n    GATE2 -->|No| LIM\n    GATE2 -->|Yes| READY[Update PR body, gh pr ready,<br/>first at-mention, CI still monitored]\n    READY --> HM2[Human starts reviewing]\n    READY --> CI{CI state}\n    CI -->|Pending| CI\n    CI -->|Red| ACT{Actionable from<br/>the repository?}\n    ACT -->|Yes| LIM\n    ACT -->|No| STOP2[Preserve the ready PR and stop]\n    CI -->|Green| GREEN[Record CI results in the PR body<br/>and post the second at-mention]\n    GREEN --> HM3[Human does the final review<br/>and decides on merge]\n```\n\n## Repository specifics must be discovered\n\nPreflight explicitly forbids hardcoding build, test, lint, or formatting commands.\n\nInstead, validation commands are discovered from `AGENTS.md`\n\n, `CLAUDE.md`\n\n, `README`\n\n, manifests such as `package.json`\n\n, build files, and CI configuration. The default branch comes from [GitHub CLI](https://cli.github.com/) via `gh repo view --json nameWithOwner,defaultBranchRef`\n\nrather than being assumed to be `main`\n\n.\n\nThe goal is for this skill to work against any repository on [GitHub](https://github.com/), not one particular repository. Hardcoding `npm test`\n\ndestroys that goal on the spot.\n\nValidation commands are not the only thing that has to be discovered, which is another thing I only added after running it. In some repositories, publishing changes does not end at a plain `git push`\n\n: there is a repository-provided signoff command or publication script to go through. The skill forbids substituting `git push`\n\nfor that procedure, and requires the publication or signoff to succeed before the draft PR is opened. The reasoning is identical to not hardcoding the test command.\n\nMaking that universal, however, breaks things in the other direction. In a repository that requires no signoff, the agent still goes looking for `gh signoff`\n\nand stops merely because the extension is not installed. So signoff is now branched on a `signoff_required`\n\nflag that defaults to false. Setting it to true requires direct evidence: repository instructions, a repository-side wrapper, or required status-check configuration.\n\nInstalling the extension locally is not evidence that the repository requires it.\n\nThat distinction is easy to get wrong. Having `gh signoff`\n\non your machine is a fact about your environment and says nothing about the repository’s publication policy. Conversely, when `signoff_required=true`\n\nis established and the command is unavailable, the run does not quietly fall back to a plain push: it stops and states that the extension or the repository wrapper is required. Neither direction gets decided by default.\n\nOnly in required mode, a paired invariant applies: every new commit invalidates the previous commit’s signoff. Even when the PR is already ready, a commit added for a review finding or a CI repair has to be pushed and signed off again for that HEAD before any of the five reviews run against it.\n\nPreflight also demands a completely clean working tree.\n\nNever stash, discard, overwrite, or absorb pre-existing changes.\n\nThe branch is created fresh from the latest remote tip of the default branch, and the run stops if a branch of that name already exists locally or remotely. No implicit reset, no implicit reuse. Same philosophy as the previous skill: the only reliable guarantee is not implementing the dangerous path in the first place.\n\n## Never let an abandoned attempt look like a successful continuation\n\nAs above, a branch of the same name stops the run. In practice, though, what actually keeps happening is messier: an earlier attempt died partway through and left behind a branch and a draft PR.\n\nThe two worst responses here are silently reusing it and silently resetting over it. Both look like a smoothly progressing second attempt, while in reality the PR gets finished carrying unverified commits from the previous run.\n\nSo reruns got their own procedure. Inspect the PR state, head branch, local branch, remote branch, and worktree first. Close a stale PR and delete its branches only when the user explicitly asks for that cleanup. After cleanup, verify that the old PR is closed, the old branch is absent both locally and remotely, the worktree is clean, and the new branch starts at the current remote tip of the default branch.\n\nThen record the old PR number and head SHA.\n\nPreserve the old PR number and head SHA in the orchestration record so the rerun is not mistaken for a successful continuation.\n\nSame motivation as refusing to open a PR while the fix-limit safety valve is active: never erase the fact that something did not work.\n\n## Three gates, then five\n\nThe loop has two stages.\n\nIn the local implementation loop, Codex implements from `/goal`\n\nplus the issue snapshot, then three independent review artifacts are captured:\n\n- Codex\n`/review`\n\nagainst the branch diff from the recorded base - Claude Code\n`/code-review`\n\nagainst the same diff - Claude Code\n`/security-review`\n\nagainst the same diff\n\nAfter the draft PR opens, the PR loop raises that to five:\n\n- The three local reviews above, rerun\n- Claude Code\n`/review #<pr-number>`\n\nagainst the PR itself - Claude Code behavior verification in a fresh worktree\n\nThe important constraint is that all five must complete against the same PR head SHA with zero high-priority findings. Without pinning the SHA, you can pass a gate in a mixed state where reviewer A saw the pre-fix code and reviewer B saw the post-fix code.\n\nAnd a failed review is explicitly not a pass.\n\nA failed or incomplete review is not a clean result.\n\nThis is the most common automation accident there is. When a review times out and produces empty output, reading that as “zero findings” lets the gate through. The stop-conditions section repeats the same point: never treat a tooling failure as approval.\n\nRunning it surfaced a nastier variant. The review prints a report that looks complete, while it actually timed out, hung, or has no recorded exit status. Because the output reads plausibly, it is more dangerous than an empty one.\n\nA review that prints a complete-looking report but times out, hangs, or has no recorded exit status is incomplete.\n\nThe fix allows retrying with a narrower read-only prompt while forbidding a partial artifact from being counted as clean.\n\nThe same section gained one more rule: CI is not a stand-in for a review.\n\nDraft-PR CI success, signoff success, or a skipped automated reviewer is not a substitute for a missing review artifact.\n\nGreen CI on the draft PR, a successful signoff, an automated reviewer that got skipped. None of them fills the hole left by a missing review artifact. Relax this and the five gates keep their shape while going hollow inside.\n\n## Fresh-worktree behavior verification\n\nThe fifth gate is different in kind. It is the only one that runs the code rather than reading the diff.\n\nIt creates a temporary [git worktree](https://git-scm.com/docs/git-worktree) at exactly the remote PR head, reads the issue and the repository instructions there, and exercises the real behavior plus the discovered validation commands. The primary worktree is never touched.\n\nThe reason for a separate worktree is that “it works here” from the machine that just built it is not credible. Intermediate files generated during implementation, caches, files that were never committed, environment variables: a tree carrying all of that is a different environment from a fresh clone. If it works in a worktree created from the remote PR head, you have at least ruled out missing commits.\n\nIf setup, checkout, or validation fails, that too counts as an incomplete gate rather than zero findings. The temporary worktree is removed after its process finishes, but its report is retained.\n\n## What not to do when signing blocks the commit\n\nThe commit-signing preflight is split out into its own reference (`references/commit-signing-preflight.md`\n\n).\n\nWhen repository-local or global configuration enables commit signing and delegates it to an external agent, such as a password manager’s SSH signer, an expired session makes the commit fail. For a human this is a non-event: unlock and carry on.\n\nThe problem is the workaround an agent reaches for. Add `--no-gpg-sign`\n\n, swap in a different signing key, rewrite the global Git configuration. All three get the commit through, and all three silently bypass the repository’s security policy. Worse, they look like success, so nobody notices later.\n\nSo all three are named and forbidden. The correct behavior is to keep the staged diff intact, stop, and ask the user to unlock the configured signer. Even after the commit succeeds, the skill verifies that it satisfies the repository’s signing policy before pushing. Only the commit SHA and verification status get recorded: no key material, account identifiers, tokens, or other secret values are printed.\n\nMy favorite line in the reference is this one.\n\nA configured account is not necessarily an active session.\n\nRead `git config`\n\n, conclude “signing is configured,” and you find out otherwise at commit time. That a setting exists and that the setting is usable right now have to be checked separately.\n\nThis is the same point as the earlier note that a tool invoked by an agent mutating the user’s environment is an incident waiting to happen. Not touching Codex’s `config.toml`\n\nand not touching Git’s global configuration are prohibited for the same reason.\n\n## Stopping at ten\n\nThe hardest part of this skill was deciding what to do when it does not converge.\n\nReview, fix, review again is a loop that can in principle never terminate. Fixing one finding surfaces another; fixing that resurrects the first. This does happen.\n\nSo there is a shared limit of ten fix iterations across the local loop, the PR loop, and CI repairs. Before an eleventh, the run stops, summarizes repeated and unresolved findings, preserves the branch, and hands the decision to the user.\n\nI raised that number from the original cap. In real use, runs that would have finished in another iteration or two got cut off by the limit far more often than runs that genuinely failed to converge. The limit exists to stop an infinite loop, not to summon a human early. When resuming by hand costs more than the extra iterations, a looser cap is the one that actually works.\n\nNever open or ready a PR while this safety valve is active.\n\nThe point is not to hide a failure to converge behind the finished appearance of a pull request.\n\nThere is one more guard, on priority normalization. `critical`\n\n, `high`\n\n, `P0`\n\n, and `P1`\n\nare treated as high priority, but promoting or demoting ambiguous findings merely to force convergence is prohibited. A cap creates an incentive for the agent to reinterpret “this is probably medium.” That escape hatch is closed explicitly.\n\n### Giving up is also a handoff\n\nWhen the limit was hit, the run used to simply end there. That was the weakest point in real use.\n\nRunning agents asynchronously means you notice they finished some time later. What is left in front of you at that point is a branch and a log that went somewhere. Which iteration it died on, what kept coming back, and how far validation had gotten are all things you have to dig the log out to learn.\n\nSo hitting the limit now requires leaving a progress comment on GitHub before stopping. If a PR already exists for the run, the comment goes on that PR; if not, it goes on the original issue, at-mentioning the login resolved from `gh api user --jq .login`\n\n. The contents are fixed:\n\n- which phase (local or PR) burned through\n`10/10`\n\n- the branch, the current HEAD SHA, and the PR URL when one exists\n- files changed and the latest validation results\n- each review artifact with its target SHA, status, and high-priority count\n- repeated findings, unresolved findings, and the exact reason convergence failed\n- the next action: the human reviews the progress and decides whether to continue by hand or start a fresh run\n\nThe comment carries a `<!-- omh-issue-loop-fix-limit:<current-head-sha> -->`\n\nmarker, existing comments are inspected first so the same marker is never posted twice, and the comment is read back afterwards to confirm the mention and marker actually landed. The same separation of “the write succeeded” from “the intended result happened” that runs through the rest of the skill.\n\nCreating a PR purely to host this comment is forbidden.\n\nThis is a stop-path handoff, not a successful completion signal.\n\nStopping is not a failure. A human not noticing that it stopped is. Stop paths in an automated workflow need as much handoff design as the success path does.\n\n## Hand it to the human as soon as the reviews pass\n\nWhat happens after all five gates come back clean got rebuilt a few times.\n\nThe first version waited for green CI, then marked the PR ready and called the human. That is the correct judgment, but in practice the wait becomes the human’s wait. In a repository where CI takes 10 minutes, every run has a 10-minute window where the reviews are all done and nobody can do anything.\n\nSo the order got swapped. The moment five reviews are clean at the same SHA, the PR is marked ready and handed to the human without waiting for CI. CI stays a required final gate, running in parallel with human review and monitored by the orchestrator in the background.\n\nThat makes the handoff two-stage. The first comment goes up right after the PR is readied:\n\nStart reviewing, but hold the merge decision for the final green-CI handoff. Human review and CI proceed independently; the merge decision is where they rejoin. The PR body carries the same note prominently: CI is still being monitored and merging must wait.\n\nThe second comment goes up when CI turns green:\n\nOnly that second comment authorizes reporting final completion and asking for the merge decision. The first one says “you may start,” not “this is done.”\n\nThe account being mentioned is whoever `gh api user --jq .login`\n\nresolves to, which is the same `gh`\n\nauthentication running the loop. No hardcoded username.\n\nThis is intentionally the same account whose\n\n`gh`\n\nauthentication runs the loop; self-mentioning that account is the explicit handoff from the agent to its human operator.\n\nThe run and the person receiving it are the same GitHub account, so GitHub’s own notifications become the wire from the agent back to you. Both comments carry SHA-scoped markers to prevent duplicates, and both are read back after posting.\n\nThe monitoring side got its own set of rules against declaring green too easily.\n\n- inspect every check with\n`gh pr checks --json name,state,bucket,link,workflow`\n\nand call it green only when every applicable check is in the`pass`\n\nbucket - failed, cancelled, timed out, action-required, stale, and missing expected checks are all not green\n- a skipped check counts only with evidence that repository configuration makes it non-applicable\n- if workflows or required checks are configured but nothing is reported, the state stays pending rather than green\n- if the repository genuinely has no CI, record that explicitly as the terminal green equivalent\n\nThe PR head SHA is re-read on every terminal observation. If it differs from the reviewed SHA, both the CI results and the review results are discarded as stale, all five reviews rerun against the new head, the PR body and handoff are rebuilt for that SHA, and monitoring restarts. Same principle as before: never assume the last state you saw is still true for an object humans touch asynchronously.\n\nWhen CI goes red, the run separates an actionable implementation failure from an external infrastructure, permission, quota, or service failure. Actionable ones count against the shared ten-fix limit, go to Codex as a restricted fix worker, and pass back through the publication gate and all five reviews. Non-actionable ones preserve the ready PR, post a comment describing the blocker, and stop. No green marker, no completion claim.\n\nNever change code to hide or bypass a failing check.\n\nThe same sentence as refusing `--no-gpg-sign`\n\nwhen signing blocks a commit, applied to CI. Tell an agent to “make it green” and deleting the test is frequently the shortest path.\n\n## Getting `Closes #N`\n\nright\n\nThe step that got the most pedantic specification is the auto-close keyword when finalizing the PR.\n\n- Write\n`Closes #<issue-number>`\n\nas its own top-level line - Keep it outside code blocks, quotes, lists, headings, and sentences\n- A bare issue URL is insufficient\n- Read the body back with\n`gh pr view`\n\nand do a simple exact-line check\n\nAll of that detail exists because, as you find out by trying it, an auto-close keyword stops firing once it sits inside a code block or a quote. Separately, [GitHub’s documentation](https://docs.github.com/en/issues/tracking-your-work-with-issues/using-issues/linking-a-pull-request-to-an-issue) is explicit that the keywords are interpreted only when the pull request targets the repository’s default branch. The keyword works only when it sits in the PR body in a form that satisfies those conditions. Ask an agent to write a PR body and it will happily tuck the keyword into a list item or embed it in a sentence for readability. The text looks right and the issue never closes.\n\nReading it back after writing exists because a successful write and the intended result are two different things.\n\n### Completion became a checklist too\n\nThat read-it-back principle now covers the whole completion step. Before declaring the run finished, a hard checklist has to pass.\n\n- All five review artifacts recorded, exit status included, against exactly the PR head SHA\n- The PR body containing review results and counts, validation results, unresolved findings, and the closing-keyword line\n- Final PR metadata re-read, with its head SHA matching the reviewed SHA\n- Verified signoff for the final head SHA only when\n`signoff_required=true`\n\n, and the recorded evidence that it was not required otherwise `gh pr ready`\n\nsucceeding, and`isDraft: false`\n\nverified- The first handoff comment verified against the reviewed SHA\n- Background CI monitoring reaching green for that same SHA, while never treating CI success as a replacement for a review gate\n- The second, CI-green at-mention verified against that SHA\n- The final report generated only after every item passes\n\nHaving run `gh pr ready`\n\nand the PR actually being ready are not the same fact. Exactly as with `Closes #N`\n\n, the successful write and the intended result have to be confirmed separately.\n\nOne more addition: if the PR changed externally, re-read it and report the discrepancy rather than relying on a stale local snapshot. The assumption that the last state you saw is still true does not survive contact with objects humans touch asynchronously. The issue is the deliberate exception: even final reporting uses the immutable preflight snapshot. What may be re-read and what must not be are separated on purpose.\n\n## Separating what you can claim to have fixed\n\nOne more rule proved its worth on dependency-alert issues.\n\nFeed the loop an issue about clearing [Dependabot](https://docs.github.com/en/code-security/concepts/supply-chain-security/dependabot-alerts) alerts and the agent wants to report “alerts resolved.” But while the PR is unmerged, the only thing resolved is the manifest and lockfile on the proposed branch. The alert count against the repository’s default branch has not dropped by one.\n\nSo the skill distinguishes the default-branch alert count from the proposed branch’s manifest and lockfile validation, forbids claiming that open alerts are resolved while the PR is unmerged, and requires recording the baseline count plus a statement that GitHub recalculates alert state after merge. Only a branch-specific API or check providing direct evidence can be used as grounds for more.\n\nGeneralized: agents want to claim that their change altered the state of an external system, when in fact they have not confirmed it. Make them separate verifiable claims from not-yet-verifiable ones. This too is a form of never treating “could not determine” as a pass.\n\n## Where the human work went\n\nThis is the part that matters.\n\nSince I started running this skill daily, the time I spend writing code has essentially vanished. Time spent reviewing dropped sharply too, aside from the final call. I keep the merge decision with a human, but by then the diff has already passed five gates.\n\nSo where did the time go? Two places.\n\n### 1. Building the foundation the loop runs on\n\nEverything this skill depends on lives in the repository:\n\n`AGENTS.md`\n\n/`CLAUDE.md`\n\naccurately describing validation commands and prohibitions- Tests that actually fail when something breaks\n- Lint and formatting runnable with a single command\n- CI configuration from which “what must pass” is readable\n- Types, so broken changes are caught statically\n\nMiss any of these and the loop appears to be running while verifying nothing. If passing the tests is the convergence condition and the tests verify nothing, the agent will keep reporting “clean” forever.\n\nIn other words, how much you can delegate to an agent is bounded by how thick your repository’s verification foundation is. Right now, only a human can build that. You can tell an agent to “improve the tests,” but what should be verified is determined by the specification, and the specification lives with the human.\n\nThat is where most of my time now goes: not implementing individual features, but putting the repository into a state where feature implementation can be delegated.\n\n### 2. Planning the issue\n\nThe other place is the issue itself.\n\nThis skill treats the issue as the single authoritative scope. It preserves the title, body, acceptance criteria, labels, and number, and stops when changes escape that scope.\n\nWhich means anything not written in the issue does not get built. Write it vaguely and it gets built vaguely. Omit acceptance criteria and the agent decides for itself what “done” means.\n\nSo writing an issue now takes incomparably longer than it used to. Concretely, it covers:\n\n- What problem this solves, and why now\n- What is in scope, and what is explicitly out\n- Acceptance criteria, stated in verifiable terms\n- Which existing code it touches, and what must not be touched\n- When there are options, which one is chosen and why the others are not\n\nThat is design work. I used to hold all of it in my head, open the editor, and decide while typing. Now it has to become prose first, because otherwise it does not get communicated.\n\nAnd once you write it down, everything you had not actually decided becomes visible. Judgment calls previously deferred with “I will figure it out while coding” all shift left into the issue-writing stage. That is the biggest effect of all. Being forced to articulate the design has done more for output quality than automating the implementation did.\n\n### Net result\n\nWriting code, writing tests, reviewing, fixing findings: all four are delegable.\n\nWhat is not delegable is deciding what to build, deciding how it should be verified, and building the foundation that makes those judgments function correctly.\n\nThe first is the plan; the second is the foundation. Human work has condensed into those two. And because their quality translates directly into output quality, the leverage is higher than before. The flip side is that sloppiness there gets mass-produced at speed.\n\n## Constraints and what is next\n\nThree constraints are intentionally left in place.\n\nOne issue per run. Running multiple issues in parallel makes branch and worktree management sharply more complex, so it is not supported.\n\nNever merging. Even with all five gates clean, the merge decision stays with a human. I have no plans to change that.\n\nReviews are largely serial. Read-only reviews may run in parallel when they cannot mutate the same worktree, but the overall shape is still close to serial. Same open problem as the previous skill, and splitting worktrees is the obvious fix.\n\n## Conclusion\n\n`omh-issue-loop`\n\nis a Hermes Agent skill that drives a single GitHub issue URL all the way to a review-ready pull request. Implementation and fixes go to Codex CLI, review and behavior verification go to Claude Code CLI, and the orchestrator never touches code.\n\nThe design essentials: split model lineages between writing and reviewing, fetch the issue once and hand every child the same snapshot, pin all five gates to the same SHA, never treat a failed review as clean, and use a ten-fix cap so a non-converging run is not disguised as a finished PR.\n\nWhat actually running it added was a single point: a one-sided separation does not hold. Keeping implementation out of the orchestrator is not enough; keeping orchestration out of the worker has to be spelled out in the prompt you hand it and verified after the process exits. A prohibition only becomes one once you verify it.\n\nThe other thing real use taught me is that handoff design is needed on the success path and the stop path alike. Reviews clean means handing the PR over immediately so human review runs alongside CI, then calling again once CI is green. Hitting the fix limit means leaving the progress on GitHub before stopping. The longer an agent runs unattended, the more that deciding when to call the human matters as much as the judgment logic itself.\n\nWhat running this loop daily taught me is that human time has moved wholesale into building the foundation the loop runs on and planning the issue. Building the machinery that can judge whether code is correct, and articulating what should be built, are now worth far more than writing the code.\n\nThat’s all from building a skill that drives one issue URL to a review-ready PR, tightening its responsibility boundary in real use, and finding that the human work has moved to foundations and planning, from the Gemba.\n\n## References\n\n[oh-my-hermes](https://github.com/codenote-net/oh-my-hermes)[Hermes Agent](https://github.com/NousResearch/hermes-agent)[Claude Code documentation](https://code.claude.com/docs/en/overview)[Codex](https://openai.com/codex/)[GitHub CLI](https://cli.github.com/)[git worktree](https://git-scm.com/docs/git-worktree)[Linking a pull request to an issue](https://docs.github.com/en/issues/tracking-your-work-with-issues/using-issues/linking-a-pull-request-to-an-issue)[Dependabot](https://docs.github.com/en/code-security/concepts/supply-chain-security/dependabot-alerts)\n\n### Pull requests behind omh-issue-loop\n\n[codenote-net/oh-my-hermes PR #6: Add issue implementation loop skill](https://github.com/codenote-net/oh-my-hermes/pull/6)[codenote-net/oh-my-hermes PR #7: Restrict issue implementation worker side effects](https://github.com/codenote-net/oh-my-hermes/pull/7)[codenote-net/oh-my-hermes PR #8: Add automatic CLI permission modes](https://github.com/codenote-net/oh-my-hermes/pull/8)[codenote-net/oh-my-hermes PR #10: Add human review mention handoff](https://github.com/codenote-net/oh-my-hermes/pull/10)[codenote-net/oh-my-hermes PR #11: Reuse an immutable issue snapshot](https://github.com/codenote-net/oh-my-hermes/pull/11)[codenote-net/oh-my-hermes PR #12: Increase the fix loop limit](https://github.com/codenote-net/oh-my-hermes/pull/12)[codenote-net/oh-my-hermes PR #13: Add a fix-limit human handoff](https://github.com/codenote-net/oh-my-hermes/pull/13)[codenote-net/oh-my-hermes PR #14: Require green CI before human handoff](https://github.com/codenote-net/oh-my-hermes/pull/14)[codenote-net/oh-my-hermes PR #15: Document the issue loop workflow](https://github.com/codenote-net/oh-my-hermes/pull/15)[codenote-net/oh-my-hermes PR #16: Make PR ready before monitoring CI](https://github.com/codenote-net/oh-my-hermes/pull/16)[codenote-net/oh-my-hermes PR #17: Make repository signoff conditional](https://github.com/codenote-net/oh-my-hermes/pull/17)", "url": "https://wpnews.pro/news/a-hermes-agent-skill-looping-between-codex-and-claude-code", "canonical_source": "https://codenote.net/en/posts/issue-to-pr-autonomous-loop-hermes-agent-skill/", "published_at": "2026-08-01 17:00:56+00:00", "updated_at": "2026-08-01 17:22:57.742831+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools", "ai-tools"], "entities": ["Tadashi Shigeoka", "Hermes Agent", "Claude Code", "Codex", "oh-my-hermes", "GitHub"], "alternates": {"html": "https://wpnews.pro/news/a-hermes-agent-skill-looping-between-codex-and-claude-code", "markdown": "https://wpnews.pro/news/a-hermes-agent-skill-looping-between-codex-and-claude-code.md", "text": "https://wpnews.pro/news/a-hermes-agent-skill-looping-between-codex-and-claude-code.txt", "jsonld": "https://wpnews.pro/news/a-hermes-agent-skill-looping-between-codex-and-claude-code.jsonld"}}