ops forgejo issue create silently no-ops on a redirecting repo (POST->GET downgrade reads as success) #160

Open
opened 2026-05-30 17:57:07 +00:00 by coilysiren · 0 comments
Owner

Summary

coily ops forgejo issue create silently creates nothing when --repo names a repo that 302-redirects (e.g. the pre-split org name coilysiren/<repo> now living under coilyco-flight-deck/<repo>). The redirect downgrades the POST to a GET, so the issues-list endpoint answers HTTP 200 with a JSON array of existing issues. coily treats the 2xx as success and prints the array, looking exactly like a successful create while nothing was created.

A create verb that silently no-ops is a real hazard - the operator (or an agent) believes the issue was filed and moves on.

Reproduction

Observed 2026-05-30 on kai-server while filing infra issues:

coily ops forgejo issue create --repo coilysiren/infrastructure --title "..." --body-file /tmp/x.md
# -> POST https://forgejo.coilysiren.me/api/v1/repos/coilysiren/infrastructure/issues returned HTTP 200: [ {issue}, {issue}, ... ]
# No issue created. The array is the existing issue LIST (newest #183), not a created object.

Retargeting the same call at the canonical owner worked and returned the created issue:

coily ops forgejo issue create --repo coilyco-flight-deck/infrastructure --title "..." --body-file /tmp/x.md
# -> https://forgejo.coilysiren.me/coilyco-flight-deck/infrastructure/issues/185  #185  [open]  ...

Root cause

Forgejo issues a 302 (not 307/308) for the moved repo. HTTP clients downgrade POST->GET on 302 by default. The POST body is dropped and the request lands on GET /repos/{owner}/{repo}/issues (list), which returns 200 + array. coily's create path keys success off the 2xx status without asserting the response is a created-issue object.

Fixes (any/all)

  1. Assert the response shape on create. A successful issue create is HTTP 201 with a single object carrying a number. Treat 200, or an array body, or a missing number, as failure - exit non-zero with a clear message naming the redirect.
  2. Do not follow cross-host/cross-owner redirects on write verbs, or follow them as 308 (method-preserving) only. A redirected write should error loudly, not silently degrade.
  3. Surface the redirect. If the API responds with a 3xx (or the final URL owner != requested owner), print "repo X redirects to Y - retarget --repo at Y" instead of swallowing it.

Fix (1) alone closes the silent-failure hazard; (2)/(3) improve the message.

Impact

Any coily forgejo write verb (issue/label/milestone/release create, issue edit/comment/close) routed at a redirecting repo name is suspect, not just issue create. Worth auditing the whole ops forgejo write surface for the same 2xx-means-success assumption.

Found during the 2026-05-30 kai-server crash investigation while filing follow-up issues.

## Summary `coily ops forgejo issue create` silently creates nothing when `--repo` names a repo that 302-redirects (e.g. the pre-split org name `coilysiren/<repo>` now living under `coilyco-flight-deck/<repo>`). The redirect downgrades the POST to a GET, so the issues-list endpoint answers `HTTP 200` with a JSON array of existing issues. coily treats the 2xx as success and prints the array, looking exactly like a successful create while nothing was created. A create verb that silently no-ops is a real hazard - the operator (or an agent) believes the issue was filed and moves on. ## Reproduction Observed 2026-05-30 on kai-server while filing infra issues: ``` coily ops forgejo issue create --repo coilysiren/infrastructure --title "..." --body-file /tmp/x.md # -> POST https://forgejo.coilysiren.me/api/v1/repos/coilysiren/infrastructure/issues returned HTTP 200: [ {issue}, {issue}, ... ] # No issue created. The array is the existing issue LIST (newest #183), not a created object. ``` Retargeting the same call at the canonical owner worked and returned the created issue: ``` coily ops forgejo issue create --repo coilyco-flight-deck/infrastructure --title "..." --body-file /tmp/x.md # -> https://forgejo.coilysiren.me/coilyco-flight-deck/infrastructure/issues/185 #185 [open] ... ``` ## Root cause Forgejo issues a 302 (not 307/308) for the moved repo. HTTP clients downgrade POST->GET on 302 by default. The POST body is dropped and the request lands on `GET /repos/{owner}/{repo}/issues` (list), which returns 200 + array. coily's create path keys success off the 2xx status without asserting the response is a created-issue object. ## Fixes (any/all) 1. **Assert the response shape on create.** A successful issue create is HTTP 201 with a single object carrying a `number`. Treat 200, or an array body, or a missing `number`, as failure - exit non-zero with a clear message naming the redirect. 2. **Do not follow cross-host/cross-owner redirects on write verbs**, or follow them as 308 (method-preserving) only. A redirected write should error loudly, not silently degrade. 3. **Surface the redirect.** If the API responds with a 3xx (or the final URL owner != requested owner), print "repo X redirects to Y - retarget --repo at Y" instead of swallowing it. Fix (1) alone closes the silent-failure hazard; (2)/(3) improve the message. ## Impact Any coily forgejo write verb (issue/label/milestone/release create, issue edit/comment/close) routed at a redirecting repo name is suspect, not just `issue create`. Worth auditing the whole `ops forgejo` write surface for the same 2xx-means-success assumption. Found during the 2026-05-30 kai-server crash investigation while filing follow-up issues.
Sign in to join this conversation.
No labels
P0
P1
P2
P3
P4
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
coilyco-bridge/coily#160
No description provided.