Skip to content

fix(git-tools): git-cli issue/pr create reported wrong number on Gitea - #174

Open
calcorum wants to merge 1 commit into
St0nefish:masterfrom
calcorum:fix/git-cli-gitea-issue-create-number
Open

fix(git-tools): git-cli issue/pr create reported wrong number on Gitea#174
calcorum wants to merge 1 commit into
St0nefish:masterfrom
calcorum:fix/git-cli-gitea-issue-create-number

Conversation

@calcorum

@calcorum calcorum commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • git-cli issue create (and pr create) against a Gitea remote reported the wrong issue/PR number and URL. The write itself always succeeded — only the JSON returned to the caller was wrong.
  • Root cause: emit_created_json() in utils/git-cli (line ~146, vendored into plugins-claude/git-tools/scripts/git-cli and plugins-claude/session/scripts/git-cli) scraped the first .../issues/N or .../pulls/N URL found anywhere in the create command's raw stdout.
  • tea issues create / tea pr create don't just print a confirmation URL — they render the whole created object first (title, author, and the full body, word-wrapped and indented two spaces), then print the real confirmation URL flush-left on its own line at the end. If the issue/PR body itself links to another issue in the same repo (extremely common — e.g. "Child of ..." for a ticket linking back to its parent), that embedded link is a URL of the exact same shape the old regex was hunting for, and it appears earlier in the output. The old head -n1 scrape grabbed it instead of the real confirmation line.
  • Verified against a real private Gitea repo: six sequential issue create calls, each with a body linking to issue Refactor: issue-driven feature workflow (replace session files, absorb git-issues) #1, all returned {"number":1,...} while the tracker had actually assigned New: git-tools script — unified gh/tea abstraction with markdown body support and CI log access #2-feat(session): require tests and post-impl steps in plans #7. The one call that returned the correct number was the very first, creating the parent map ticket itself — its body had nothing to link to yet, so the old scrape had no decoy URL to grab and fell through correctly to the real trailing confirmation line. This isn't a coincidence that happens to fit the mechanism; it's what the mechanism predicts for that specific call.

Fix

In emit_created_json():

  1. Primary match: anchor to a line containing nothing but the URL (^[[:space:]]*URL[[:space:]]*$), and take the last such match. tea's real confirmation line is reliably the only line printed flush-left with nothing else on it — every title/body line tea renders is indented, so this alone rejects a markdown-link decoy embedded mid-body. Taking the last match (not first) also correctly picks the true confirmation over a body that happens to end with a bare URL on its own line (e.g. a "See also " reference) — that shape does match the anchor, so only last-match position tells it apart.
  2. Fallback: if the anchored pattern finds nothing (an output shape not verified against real tea/gh), fall back to the old permissive "URL anywhere" scrape — but still take the last match, never the first. This keeps an unrecognized shape degrading to old best-effort behavior rather than a hard die on a write that already succeeded, which would be worse than the original bug.
  3. gh's output is just the bare URL with nothing else, so both paths are a no-op there — GitHub was never affected.

pr create normalizes through the same emit_created_json() function for both platforms, so it shared the exact defect and is fixed by the same change. I did not independently observe real tea pr create output (creating a throwaway PR wasn't in scope for this fix) — tea's own print package sources IssueDetails/PullDetails from the same table/markdown renderer (confirmed via strings on the tea binary), so the rendering shape should match, but this is inference rather than direct observation. The fallback path exists specifically so that if this inference is wrong, the wrapper degrades safely instead of breaking pr create outright.

Verification

  • Reproduced the bug live: ran tea issues create and captured its raw output — confirmed the body is rendered indented, before a flush-left confirmation URL.
  • Built a simulated repro using a body that links back to issue Refactor: issue-driven feature workflow (replace session files, absorb git-issues) #1 (mirroring this repo's real ticket convention) and confirmed the old regex grabs #1 while the new one correctly grabs the created object's real number.
  • Ran the fix against the vendored plugin copy (post utils/sync.sh) and confirmed correct output on: the real captured tea output, the simulated body-links-to-parent case, a decoy where the body itself ends with a bare URL on its own line, and the fallback path (URL only ever inline in prose, decoy first).
  • Created two throwaway test issues on cal/quasimorph-custom-clone-names (titled TEST — git-cli issue number repro (safe to close)) to establish ground truth for real tea output — both closed immediately after reading the result. Did not touch any of the repo's 12 existing real issues.
  • Added a regression test to tests/git-cli/test-issue-write-json.sh: replaced the suite's synthetic one-line tea mocks for issues create / pr create with realistic multi-line output carrying two decoys (a markdown-link-in-body and a bare-URL body line). Confirmed this test fails against the pre-fix code (gitea issue create and gitea pr create both report #1) and passes against the fix.
  • Ran the full suite (tests/test.sh, 30 suites). All git-cli/git-tools/session suites pass — including the three tests/session/* suites that reference utils/git-cli (test-ci-poll.sh, test-pr-wait.sh, test-pr-auto-merge-status.sh), re-run at the final committed state. One pre-existing, unrelated failure in permission-manager/test-classify (9 assertions): verified independently in a throwaway git worktree checked out at unmodified origin/master — same 9 failures, so it's confirmed unrelated to this branch, not merely inferred from an unchanged-files argument.
  • utils/sync.sh --check and the repo's .githooks/pre-commit both pass — vendored copies (git-tools, session) are in sync with canonical utils/git-cli.
  • Bumped git-tools 2.2.1 → 2.2.2 and session 4.5.0 → 4.5.1 in both plugins-claude and plugins-copilot, matching the versioning convention used by prior git-cli bugfix commits (e.g. bc95efd, f52d049, 45f84a4).

Test plan

  • bash tests/test.sh — full suite, expect only the pre-existing permission-manager/test-classify failure
  • bash tests/git-cli/test-issue-write-json.sh — 18/18 pass
  • Optionally: create and immediately close a real test issue/PR against a live Gitea repo whose ticket bodies link to other issues, and confirm the reported number matches what the tracker actually assigned

`emit_created_json` scraped the first issue/pull URL found anywhere in a
create command's raw output. `tea issues create` / `tea pr create` render
the whole created object — title, author, and the full body, indented —
before the flush-left confirmation URL. When the body itself links to
another issue in the same repo (e.g. "Child of ...St0nefish/issues/1", the normal
shape for a ticket linking back to a parent), that embedded link matches
the same URL pattern and appears earlier in the output, so the wrapper
silently reported the linked issue's number instead of the one just
created. Reproduced against a real Gitea repo: six sequential `issue
create` calls, each with a body linking to issue St0nefish#1, all returned
`{"number":1}` while the tracker actually assigned St0nefish#2-St0nefish#7. `pr create`
shares the same normalizer and is fixed by the same change.

- Anchor the primary match to a line containing nothing but the URL and
  take the last such match — tea's confirmation line is printed flush
  left with nothing else on it, while every title/body line is indented.
  gh's output is just the bare URL, so this is a no-op there.
- Keep the old permissive "URL anywhere" scrape as a fallback (last
  match, not first) for any output shape the anchor doesn't recognize,
  so an unverified shape degrades to the old best-effort behavior
  instead of a hard failure on a write that already succeeded.
- tests/git-cli/test-issue-write-json.sh: replace the synthetic
  one-line tea mocks with realistic multi-line output carrying two
  decoys per the design above, and confirm the suite fails against the
  pre-fix code and passes against the fix.
- bump git-tools 2.2.1->2.2.2 and session 4.5.0->4.5.1 (both claude and
  copilot flavors; both vendor/reference git-cli); re-sync vendored
  copies

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant