A create whose text gh's JSON sanitizer rewrites is recorded as a conflict on the controller's own object (found on PR #314)
Maintainers usually reply within 1 day
A pull request for this has already been merged.
- #330 by @iceboundrock — merged
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 20/100
Research direction
Start from the controller's create read-back code implementing ADR 0004 where byte comparison with gh output occurs. Read go-gh's sanitizerRoundTripper to confirm the rewrite, then add a read-only REST GET using gh auth token for K2/K4/K5/K7/K8/K9. Run new tests A-G with fakes modelling the sanitizer; done when all pass, docs explain the limitation, and an older blocked run recovers via unblock without rewriting.
Written by the indexing model from the issue text.
Description
Artifact: bug report. The bug was found when run af-20261025T011823Z-8df69a (#284, PR #314) blocked on its own PR. The fix, its tests and the run's recovery evidence follow in a PR that closes this issue. The working copy is a local branch in /home/ruoshi/code/github/.worktree/gh-exact-read.
Summary
A create's completion read-back (ADR 0004) compares the payload byte for byte with what gh prints. gh does not print GitHub's text as it is: go-gh's JSON sanitizer rewrites control characters, their JSON escapes and the literal text of an escape into caret notation. So:
- False conflict. A PR, issue or comment that the controller created byte for byte reads back as another text. The record becomes a
conflicton the controller's own object, and the phase blocks.unblockonly repeats the same comparison and blocks again. - False match. A payload holding caret text (
^C,^[) reads back equal from an object that holds the control character (\x03,\x1b). This case did not occur on #284, but the same comparison allows it.
Evidence (PR #314)
- The ANALYZE_EXECUTE agent's PR body explained terminal output and held the six characters
\u001bas text (as in\u001b[0m). - The journal's K2 payload: 4408 characters, sha256
7e88c7877c7791e81bd117b39fe2a87f647e0c24e0d52b48622840ac8978d459. gh pr list --json bodyread-back: 4405 characters, sha256d0480d77…, three characters shorter per\u001b.- REST
GET /repos/iceboundrock/AutoForge/pulls/314(nogh): 4408 characters, sha2567e88c787…, equal to the journal. The create landed byte for byte. - Head
autoforge/284@2f676de8, basemain, repository, state and marker all matched. Only the body differed, and only ongh's read. - The run blocked with K2 in
conflict. An earlierunblockre-blocked the same way.
Root cause
- gh 2.103.2 links go-gh v2.17.0. Its API client installs
asciisanitizer.Sanitizer{JSON: true}on the HTTP transport (sanitizerRoundTripper), and the rewrite happens on the reply's bytes before any JSON is decoded. - Each C0/C1 control character and its JSON escape become caret notation. So does the text of an escape: the wire form
\\u001b(the body's literal\u001b) becomes\\^[, which decodes to\^[. gh's cache and itsGH_DEBUGlog see the rewritten reply. No flag, Accept type orghcommand turns the sanitizer off.- The rewrite is lossy and ambiguous: the literal text
\^[prints as\^[too. No comparison ofgh's print can therefore prove an object's text, and normalising either side would accept bodies the controller never wrote.
Expected behaviour
| Case | Result |
|---|---|
gh's read equals the payload, which holds no ^ |
OBSERVE, with no extra request |
One holder, every non-text condition holds, and the text differs on gh's read (or the payload holds a ^) |
read that one object exactly: equal → OBSERVE; different → CONFLICT |
| The exact read is unavailable or inconclusive | explicit failure; never another create or edit |
| Two or more holders of one identity | CONFLICT, with no exact read |
| A non-text condition fails (head, base, repo, state, marker, watermark) | CONFLICT, with no exact read |
Equality stays byte for byte. No .strip(), .replace() or Unicode normalisation, \u001b and \^[ are never treated as equal, and the persisted payload is never changed to fit a read-back. gh's terminal protection stays on for everything gh prints.
Acceptance criteria
- The exact read is a read-only REST
GETof one object underhttps://api.github.com/, using the credentialghholds. The credential stays in memory only. The read follows no redirect, is bounded in size and time, and fails closed on authentication, network, JSON and wrong-object errors. - It applies to the create read-backs of K2, K4, K5, K7, K8 and K9.
- Tests:
- A: the sanitizer's rewrite → OBSERVE.
- B: genuine caret text or control characters → CONFLICT.
- C: an exact read that is unavailable or inconclusive (timeout, 401/403/404, invalid JSON, oversize,
gh auth tokenfailure) → no create or edit. - D: duplicate holders → CONFLICT.
- E: crash after a landed create → resume without an agent, push or create.
- F: an ordinary body → no extra request.
- G: hostile text is never quoted in a reason, error or log.
- The fakes model the sanitizer with the same semantics as go-gh.
- Documentation explains why
gh's JSON cannot prove identity, what stays strict, why terminal protection is unaffected, and how to troubleshoot. - A run blocked this way by an earlier version recovers with
unblock, with nothing written again.
- Dominant language
- Python
- Stars
- 0
- Forks
- 0
- Avg merge
- 5h 51m
- Merged PRs (30d)
- 73
Getting set up
This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from iceboundrock/AutoForge
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
iceboundrock/AutoForge#350 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
iceboundrock/AutoForge#304 ·
Maintainers usually reply within 1 day
-
Difficulty 5/5 Over a week Newbie friendliness 25/100
iceboundrock/AutoForge#375 ·
Maintainers usually reply within 1 day
-
Difficulty 5/5 Over a week Newbie friendliness 25/100
iceboundrock/AutoForge#374 ·
Maintainers usually reply within 1 day
-
Difficulty 5/5 Over a week Newbie friendliness 30/100
iceboundrock/AutoForge#373 ·
Maintainers usually reply within 1 day
All issues in iceboundrock/AutoForge
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
btclib-org/btclib-wallet#587 · 1 comment ·
Maintainers usually reply within 1 day
-
documentation
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
Judgment-Pack/judgment-pack-spec#275 ·
Maintainers usually reply within 1 day
-
ai-generated
Difficulty 2/5 1-3 hours Newbie friendliness 77/100
Maintainers usually reply within 1 day
-
documentation good first issue priority: low size: XS
Difficulty 1/5 1-3 hours Newbie friendliness 92/100
yeongseon/azure-functions-doctor-python#651 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
Changaco/python-libarchive-c#146 ·