fix: keep request payloads out of the issue store #23

Merged
claude merged 3 commits from fix/no-store-for-label-payloads into main 2026-08-10 13:24:35 +00:00
Collaborator

Closes #19.

labels.py passed the issue store to _gitea.api for one reason only: the
request file needed somewhere to live. On a checkout without a store that
silently created tmp/issues/.payload/ — a store materialized as a side effect
of an operation that has nothing to do with issues, and nothing said so.

What changed

Where a request body goes is now the transport's decision, not the caller's.

  • _gitea.py resolves PAYLOAD_ROOT (<repo>/tmp/payload) from its own
    location, the same way issue.store_root and page.store_root do, so every
    caller writes to one directory whatever it was invoked from.
  • out_root is gone from api() and add_dependency(), and from all six call
    sites: labels.py, push.py (x3), comment.py (x2), wiki_push.py (x2).
    Sync and wiki move together — no mixed state.
  • The directory is created by the first write of a run and not before, so a
    --dry-run or a run with nothing to send leaves nothing behind.
  • tmp/ is already gitignored, so the new location is covered.
  • The name carries the distinction the old path lost: a visible payload/
    beside issues/ and wiki/, not a dotdir among an issue's files. A store
    holds the only copy of something; this holds debris kept for a retry.

Docs updated: AGENTS.md (a new "Request payloads" section, plus labels.py
in the repo layout), skills/sync/SKILL.md, skills/wiki/SKILL.md.

Acceptance criteria

  • labels.py neither creates nor requires the issue store on any path
  • the payload directory is not the issue store, and is named so that this
    is visible
  • every other script in the sync layer writes payloads to the same place —
    they all moved together
  • .gitignore covers the new location (it is under tmp/)
  • a test proves labels.py on a tree without tmp/issues does not create
    it

Tests

tests/test_payload_root.py (new, 12 tests) runs the real labels.py inside a
throwaway repository with a fake tea first on PATH — no network, and the
developer's own store is never in reach. It asserts that no store appears, that
the payloads land in tmp/payload/ and are the bodies actually sent, that a
dry run writes nothing, and that a run from a subdirectory still resolves to the
repo root. Two source checks keep the callers from drifting apart again.

Against the pre-fix tree these fail with labels.py created the issue store.

python3 -m unittest discover -s tests -v
Ran 244 tests in 5.3s
OK

🤖 Generated with Claude Code

Closes #19. `labels.py` passed the issue store to `_gitea.api` for one reason only: the request file needed somewhere to live. On a checkout without a store that silently created `tmp/issues/.payload/` — a store materialized as a side effect of an operation that has nothing to do with issues, and nothing said so. ## What changed Where a request body goes is now the transport's decision, not the caller's. - `_gitea.py` resolves `PAYLOAD_ROOT` (`<repo>/tmp/payload`) from its own location, the same way `issue.store_root` and `page.store_root` do, so every caller writes to one directory whatever it was invoked from. - `out_root` is gone from `api()` and `add_dependency()`, and from all six call sites: `labels.py`, `push.py` (x3), `comment.py` (x2), `wiki_push.py` (x2). Sync and wiki move together — no mixed state. - The directory is created by the first write of a run and not before, so a `--dry-run` or a run with nothing to send leaves nothing behind. - `tmp/` is already gitignored, so the new location is covered. - The name carries the distinction the old path lost: a visible `payload/` beside `issues/` and `wiki/`, not a dotdir among an issue's files. A store holds the only copy of something; this holds debris kept for a retry. Docs updated: `AGENTS.md` (a new "Request payloads" section, plus `labels.py` in the repo layout), `skills/sync/SKILL.md`, `skills/wiki/SKILL.md`. ## Acceptance criteria - [x] `labels.py` neither creates nor requires the issue store on any path - [x] the payload directory is not the issue store, and is named so that this is visible - [x] every other script in the sync layer writes payloads to the same place — they all moved together - [x] `.gitignore` covers the new location (it is under `tmp/`) - [x] a test proves `labels.py` on a tree without `tmp/issues` does not create it ## Tests `tests/test_payload_root.py` (new, 12 tests) runs the real `labels.py` inside a throwaway repository with a fake `tea` first on `PATH` — no network, and the developer's own store is never in reach. It asserts that no store appears, that the payloads land in `tmp/payload/` and are the bodies actually sent, that a dry run writes nothing, and that a run from a subdirectory still resolves to the repo root. Two source checks keep the callers from drifting apart again. Against the pre-fix tree these fail with `labels.py created the issue store`. ``` python3 -m unittest discover -s tests -v Ran 244 tests in 5.3s OK ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code)
claude added 1 commit 2026-08-10 12:26:41 +00:00
`labels.py` handed `_gitea.api` the issue store as a place to put the
request file, and on a checkout without a store that quietly created
`tmp/issues/.payload/`. Bootstrapping a repository's labels touches no
issue at all, so the one rule the store has — nothing materializes it as
a side effect of a write — was broken by an operation that has no
business knowing the store exists.

Where a request body goes was never the caller's decision to make. It is
now the transport's: `tmp/payload/`, resolved from `_gitea.py`'s own
location the way both domains resolve theirs, so every caller — sync and
wiki alike — writes to one directory whatever it was invoked from, and
`out_root` is gone from `api`, `add_dependency` and all six call sites.
The directory is created by the first write of a run and not before: a
`--dry-run` leaves nothing behind. `tmp/` is already gitignored.

The name carries the distinction the old path lost. A store holds the
only copy of something; this holds debris kept for a retry or a
post-mortem, and deleting it costs nothing. A dotdir sitting among an
issue's files claimed otherwise, and `ls tmp/issues` started lying about
what existed.

tests/test_payload_root.py runs the real `labels.py` in a throwaway repo
against a fake `tea` on PATH: no store appears, the payloads land in
tmp/payload/, a dry run writes nothing, and a run from a subdirectory
still resolves to the repo root. Two source checks keep the callers from
drifting apart again.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
naudachu added 1 commit 2026-08-10 12:32:38 +00:00
One test stubs the transport a layer below `api()` — at `subprocess`, to
exercise the path a 422 really takes — so it reaches the real payload
write. That used to land in the test's own temp store, because the caller
named the directory; now the directory is `_gitea.PAYLOAD_ROOT`, resolved
from the module's location, and the file appeared in the developer's
`tmp/payload/`.

`StoreTestCase` patches `PAYLOAD_ROOT` to its fixture alongside the other
seams, and the rule in AGENTS.md gains the third directory a test must
not write to.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
naudachu added 1 commit 2026-08-10 13:23:42 +00:00
claude merged commit 40016e06f2 into main 2026-08-10 13:24:35 +00:00
Sign in to join this conversation.