cd /news/ai-agents/a-hermes-agent-skill-looping-between… · home topics ai-agents article
[ARTICLE · art-83161] src=codenote.net ↗ pub= topic=ai-agents verified=true sentiment=· neutral

A Hermes Agent Skill Looping Between Codex and Claude Code

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.

read32 min views1 publishedAug 1, 2026

Tadashi Shigeoka· Thu, July 23, 2026

Last time I wrote omh-pr-multi-review

, a skill that has Claude Code and Codex independently review the same pull request (Reviewing a PR with Claude Code and Codex at Once). That automated a step that happens after a PR already exists.

This time I went upstream and automated everything from the issue to the PR. I added omh-issue-loop

to oh-my-hermes, my collection of customizations for Hermes Agent (PR #6).

Usage 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.

I 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.

I 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.

What the skill contains #

That is the whole of it.

The previous skill, omh-pr-multi-review

, 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.

That 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.

An 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.

The orchestrator never implements #

SKILL.md opens not with what to do but with what not to do.

Orchestrate 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.

That separation is the core of the design, for two reasons.

First, 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.

Second, 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.

The worker never orchestrates either #

The 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.

Hand Codex a bare /goal <issue-url>

and 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.

That is not Codex misbehaving. Given an issue and a /goal

, 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.

So the responsibility boundary became its own section.

Treat Codex implementation and fix processes as working-tree workers, not autonomous issue owners.

A 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.

Commits, 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.

Never rely on the worker to infer the boundary from the surrounding workflow.

That 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.

The prompt actually passed is nothing clever, just the prohibitions spelled out.

The 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.

The issue is read exactly once #

In the prompt above, the ISSUE SNAPSHOT

block sits ahead of the prohibitions.

The orchestrator is the only process allowed to fetch the issue, and it does so exactly once, during preflight.

That 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.

The 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

or gh api

against the issue is forbidden.

Passing only the issue URL is insufficient.

That 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.

For 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.

A prohibition only becomes one once you verify it #

Writing a prohibition into a prompt and having it observed are two different things.

Every 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.

So the skill now snapshots state before and after every worker process and compares. Before, it captures:

  • the current HEAD SHA and git log --oneline <base-sha>..HEAD

  • the current branch name and that branch’s reflog git status --short --branch

and the tracked, staged, and untracked file sets- the remote branch OID from git ls-remote --heads origin <branch-name>

, recording absence as a value - every PR for the branch from gh pr list --repo <owner>/<repo> --head <branch-name> --state all

, plus enoughgh pr view

metadata to detect edits, readiness, state, body, title, and head changes - the immutable issue snapshot captured during preflight, never re-fetched for the baseline

The 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.

Once 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.

On 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

that 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.

If any check fails, or cannot be completed, the run stops immediately.

Do not undo or conceal the worker action.

That 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.

Treating 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.

Fixed execution settings #

The delegation settings are hardcoded in SKILL.md.

Running the implementing Codex at model_reasoning_effort="low"

and service_tier="fast"

while the reviewing Claude Code gets --effort high

is deliberate.

Given 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.

Pinning the permission mode turned out to be mandatory in practice: --yolo

for Codex, --permission-mode auto

for 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.

What 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

and 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.

As with the previous skill, Codex settings are injected per process via -c

and the global config.toml

is never touched. A tool invoked by an agent that mutates the user’s environment is an incident waiting to happen.

The overall flow #

Before the procedure itself, SKILL.md sends the agent to references/workflow-flowchart.md

first. 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.

Rearranged for this post, the whole thing looks like this.

flowchart TD
    IN[Receive issue URL] --> PRE[Preflight: CLIs, auth,<br/>repository match, clean tree,<br/>discover validation and signoff]
    PRE -->|Fail| STOP[Stop and ask the user]
    PRE -->|Pass| SNAP[Run gh issue view exactly once<br/>Save the immutable snapshot]
    SNAP --> BR[Branch omh/issue-N-topic<br/>from default branch tip]
    BR --> BASE[Capture side-effect baseline<br/>HEAD, reflog, remote, PRs]
    BASE --> IMPL[Codex implements under the<br/>restricted prompt, uncommitted]
    IMPL --> CHK{Side-effect check<br/>fail-closed}
    CHK -->|Violation| ABORT[Stop, report before/after<br/>never undo or conceal]
    CHK -->|Clean| L1[Codex /review]
    CHK -->|Clean| L2[Claude /code-review]
    CHK -->|Clean| L3[Claude /security-review]
    L1 --> GATE1{Zero high-priority?}
    L2 --> GATE1
    L3 --> GATE1
    GATE1 -->|No| LIM{Shared fix count<br/>below 10?}
    LIM -->|Yes| FIX[Codex fixes high-priority only]
    FIX --> BASE
    LIM -->|No| HAND[Stop: at-mention progress<br/>on the PR, or the issue]
    HAND --> HM1[Human decides how to continue]
    GATE1 -->|Yes| SIGN[Signing preflight, commit, push]
    SIGN --> SO{signoff_required?}
    SO -->|Yes| SOOK[Sign off and verify<br/>status for that SHA]
    SOOK --> PR
    SO -->|No| PR[Run full validation, open draft PR]
    PR --> P1[Codex /review]
    PR --> P2[Claude /code-review]
    PR --> P3[Claude /security-review]
    PR --> P4[Claude /review #N]
    PR --> P5[Fresh-worktree behavior verification]
    P1 --> GATE2{All five sources,<br/>same SHA, zero high?}
    P2 --> GATE2
    P3 --> GATE2
    P4 --> GATE2
    P5 --> GATE2
    GATE2 -->|No| LIM
    GATE2 -->|Yes| READY[Update PR body, gh pr ready,<br/>first at-mention, CI still monitored]
    READY --> HM2[Human starts reviewing]
    READY --> CI{CI state}
    CI -->|Pending| CI
    CI -->|Red| ACT{Actionable from<br/>the repository?}
    ACT -->|Yes| LIM
    ACT -->|No| STOP2[Preserve the ready PR and stop]
    CI -->|Green| GREEN[Record CI results in the PR body<br/>and post the second at-mention]
    GREEN --> HM3[Human does the final review<br/>and decides on merge]

Repository specifics must be discovered #

Preflight explicitly forbids hardcoding build, test, lint, or formatting commands.

Instead, validation commands are discovered from AGENTS.md

, CLAUDE.md

, README

, manifests such as package.json

, build files, and CI configuration. The default branch comes from GitHub CLI via gh repo view --json nameWithOwner,defaultBranchRef

rather than being assumed to be main

.

The goal is for this skill to work against any repository on GitHub, not one particular repository. Hardcoding npm test

destroys that goal on the spot.

Validation 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

: there is a repository-provided signoff command or publication script to go through. The skill forbids substituting git push

for 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.

Making that universal, however, breaks things in the other direction. In a repository that requires no signoff, the agent still goes looking for gh signoff

and stops merely because the extension is not installed. So signoff is now branched on a signoff_required

flag that defaults to false. Setting it to true requires direct evidence: repository instructions, a repository-side wrapper, or required status-check configuration.

Installing the extension locally is not evidence that the repository requires it.

That distinction is easy to get wrong. Having gh signoff

on your machine is a fact about your environment and says nothing about the repository’s publication policy. Conversely, when signoff_required=true

is 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.

Only 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.

Preflight also demands a completely clean working tree.

Never stash, discard, overwrite, or absorb pre-existing changes.

The 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.

Never let an abandoned attempt look like a successful continuation #

As 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.

The 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.

So 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.

Then record the old PR number and head SHA.

Preserve the old PR number and head SHA in the orchestration record so the rerun is not mistaken for a successful continuation.

Same motivation as refusing to open a PR while the fix-limit safety valve is active: never erase the fact that something did not work.

Three gates, then five #

The loop has two stages.

In the local implementation loop, Codex implements from /goal

plus the issue snapshot, then three independent review artifacts are captured:

  • Codex /review

against the branch diff from the recorded base - Claude Code /code-review

against the same diff - Claude Code /security-review

against the same diff

After the draft PR opens, the PR loop raises that to five:

  • The three local reviews above, rerun
  • Claude Code /review #<pr-number>

against the PR itself - Claude Code behavior verification in a fresh worktree

The 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.

And a failed review is explicitly not a pass.

A failed or incomplete review is not a clean result.

This 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.

Running 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.

A review that prints a complete-looking report but times out, hangs, or has no recorded exit status is incomplete.

The fix allows retrying with a narrower read-only prompt while forbidding a partial artifact from being counted as clean.

The same section gained one more rule: CI is not a stand-in for a review.

Draft-PR CI success, signoff success, or a skipped automated reviewer is not a substitute for a missing review artifact.

Green 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.

Fresh-worktree behavior verification #

The fifth gate is different in kind. It is the only one that runs the code rather than reading the diff.

It creates a temporary 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.

The 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.

If 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.

What not to do when signing blocks the commit #

The commit-signing preflight is split out into its own reference (references/commit-signing-preflight.md

).

When 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.

The problem is the workaround an agent reaches for. Add --no-gpg-sign

, 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.

So 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.

My favorite line in the reference is this one.

A configured account is not necessarily an active session.

Read git config

, 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.

This 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

and not touching Git’s global configuration are prohibited for the same reason.

Stopping at ten #

The hardest part of this skill was deciding what to do when it does not converge.

Review, 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.

So 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.

I 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.

Never open or ready a PR while this safety valve is active.

The point is not to hide a failure to converge behind the finished appearance of a pull request.

There is one more guard, on priority normalization. critical

, high

, P0

, and P1

are 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.

Giving up is also a handoff

When the limit was hit, the run used to simply end there. That was the weakest point in real use.

Running 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.

So 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

. The contents are fixed:

  • which phase (local or PR) burned through 10/10

  • the branch, the current HEAD SHA, and the PR URL when one exists

  • files changed and the latest validation results

  • each review artifact with its target SHA, status, and high-priority count

  • repeated findings, unresolved findings, and the exact reason convergence failed

  • the next action: the human reviews the progress and decides whether to continue by hand or start a fresh run

The comment carries a <!-- omh-issue-loop-fix-limit:<current-head-sha> -->

marker, 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.

Creating a PR purely to host this comment is forbidden.

This is a stop-path handoff, not a successful completion signal.

Stopping 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.

Hand it to the human as soon as the reviews pass #

What happens after all five gates come back clean got rebuilt a few times.

The 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.

So 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.

That makes the handoff two-stage. The first comment goes up right after the PR is readied:

Start 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.

The second comment goes up when CI turns green:

Only that second comment authorizes reporting final completion and asking for the merge decision. The first one says “you may start,” not “this is done.”

The account being mentioned is whoever gh api user --jq .login

resolves to, which is the same gh

authentication running the loop. No hardcoded username.

This is intentionally the same account whose

gh

authentication runs the loop; self-mentioning that account is the explicit handoff from the agent to its human operator.

The 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.

The monitoring side got its own set of rules against declaring green too easily.

  • inspect every check with gh pr checks --json name,state,bucket,link,workflow

and call it green only when every applicable check is in thepass

bucket - failed, cancelled, timed out, action-required, stale, and missing expected checks are all not green

  • a skipped check counts only with evidence that repository configuration makes it non-applicable
  • if workflows or required checks are configured but nothing is reported, the state stays pending rather than green
  • if the repository genuinely has no CI, record that explicitly as the terminal green equivalent

The 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.

When 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.

Never change code to hide or bypass a failing check.

The same sentence as refusing --no-gpg-sign

when signing blocks a commit, applied to CI. Tell an agent to “make it green” and deleting the test is frequently the shortest path.

Getting Closes #N #

right

The step that got the most pedantic specification is the auto-close keyword when finalizing the PR.

  • Write Closes #<issue-number>

as its own top-level line - Keep it outside code blocks, quotes, lists, headings, and sentences

  • A bare issue URL is insufficient
  • Read the body back with gh pr view

and do a simple exact-line check

All 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 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.

Reading it back after writing exists because a successful write and the intended result are two different things.

Completion became a checklist too

That read-it-back principle now covers the whole completion step. Before declaring the run finished, a hard checklist has to pass.

  • All five review artifacts recorded, exit status included, against exactly the PR head SHA
  • The PR body containing review results and counts, validation results, unresolved findings, and the closing-keyword line
  • Final PR metadata re-read, with its head SHA matching the reviewed SHA
  • Verified signoff for the final head SHA only when signoff_required=true

, and the recorded evidence that it was not required otherwise gh pr ready

succeeding, andisDraft: false

verified- The first handoff comment verified against the reviewed SHA

  • Background CI monitoring reaching green for that same SHA, while never treating CI success as a replacement for a review gate
  • The second, CI-green at-mention verified against that SHA
  • The final report generated only after every item passes

Having run gh pr ready

and the PR actually being ready are not the same fact. Exactly as with Closes #N

, the successful write and the intended result have to be confirmed separately.

One 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.

Separating what you can claim to have fixed #

One more rule proved its worth on dependency-alert issues.

Feed the loop an issue about clearing Dependabot 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.

So 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.

Generalized: 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.

Where the human work went #

This is the part that matters.

Since 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.

So where did the time go? Two places.

1. Building the foundation the loop runs on

Everything this skill depends on lives in the repository:

AGENTS.md

/CLAUDE.md

accurately describing validation commands and prohibitions- Tests that actually fail when something breaks

  • Lint and formatting runnable with a single command
  • CI configuration from which “what must pass” is readable
  • Types, so broken changes are caught statically

Miss 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.

In 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.

That 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.

2. Planning the issue

The other place is the issue itself.

This 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.

Which 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.

So writing an issue now takes incomparably longer than it used to. Concretely, it covers:

  • What problem this solves, and why now
  • What is in scope, and what is explicitly out
  • Acceptance criteria, stated in verifiable terms
  • Which existing code it touches, and what must not be touched
  • When there are options, which one is chosen and why the others are not

That 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.

And 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.

Net result

Writing code, writing tests, reviewing, fixing findings: all four are delegable.

What is not delegable is deciding what to build, deciding how it should be verified, and building the foundation that makes those judgments function correctly.

The 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.

Constraints and what is next #

Three constraints are intentionally left in place.

One issue per run. Running multiple issues in parallel makes branch and worktree management sharply more complex, so it is not supported.

Never merging. Even with all five gates clean, the merge decision stays with a human. I have no plans to change that.

Reviews 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.

Conclusion #

omh-issue-loop

is 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.

The 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.

What 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.

The 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.

What 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.

That’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.

References #

oh-my-hermesHermes AgentClaude Code documentationCodexGitHub CLIgit worktreeLinking a pull request to an issueDependabot

Pull requests behind omh-issue-loop

codenote-net/oh-my-hermes PR #6: Add issue implementation loop skillcodenote-net/oh-my-hermes PR #7: Restrict issue implementation worker side effectscodenote-net/oh-my-hermes PR #8: Add automatic CLI permission modescodenote-net/oh-my-hermes PR #10: Add human review mention handoffcodenote-net/oh-my-hermes PR #11: Reuse an immutable issue snapshotcodenote-net/oh-my-hermes PR #12: Increase the fix loop limitcodenote-net/oh-my-hermes PR #13: Add a fix-limit human handoffcodenote-net/oh-my-hermes PR #14: Require green CI before human handoffcodenote-net/oh-my-hermes PR #15: Document the issue loop workflowcodenote-net/oh-my-hermes PR #16: Make PR ready before monitoring CIcodenote-net/oh-my-hermes PR #17: Make repository signoff conditional

── more in #ai-agents 4 stories · sorted by recency
── more on @tadashi shigeoka 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/a-hermes-agent-skill…] indexed:0 read:32min 2026-08-01 ·