Skip to content

Phase 3: add items and mark them consumed from an issue form - #4

Merged
sr320 merged 1 commit into
mainfrom
phase3-issue-forms
Aug 3, 2026
Merged

Phase 3: add items and mark them consumed from an issue form#4
sr320 merged 1 commit into
mainfrom
phase3-issue-forms

Conversation

@sr320

@sr320 sr320 commented Aug 3, 2026

Copy link
Copy Markdown
Member

Adding or updating an item now needs no git knowledge and takes about 30 seconds. Base is main, which already has Phases 1–2.

Three issue forms — add an item, change status, verify a location — are parsed by scripts/apply_issue.py, which edits items.csv and hands issue-to-pr.yml a PR title and body. The issue closes when the PR merges.

One plan change worth your agreement

The plan called for location dropdowns generated from locations.csv. I dropped that. 173 options is miserable on a phone, and regenerating form YAML from data would mean the repo editing its own workflow files on every data change.

Instead the site links to issues/new?template=add-item.yml&location=213-DRW-27 — GitHub prefills by field id. Scan the QR sticker on a drawer → tap "Add an item here" → the location is already filled in. Free-text locations are validated by the parser, which suggests close matches on a typo:

213-DRW-99 is not a known location. […]
Closest matches:

  • 213-DRW-59 — Drawer 59
  • 213-DRW-29 — Drawer 29

The site gains three actions: Add an item here and I verified this on the location banner, used up? on every item row.

Field labels are the contract, so CI now enforces it

GitHub renders forms as ### <label> sections, so a label is what the parser matches on. Rename one and the parser silently stops seeing that field — no error, the value just disappears into the void.

scripts/check_forms.py runs in CI and fails on any form field the parser doesn't read, any handler with no form, and any field missing an id (which would break prefill). It immediately caught a real instance: verify-location.yml's free-text field is labelled "Anything there that isn't listed", which no handler read — so things found but not recorded, the most valuable output of a verification pass, would never have reached the PR body. Now surfaced there explicitly.

Nothing is written when input is bad

Unknown location, a name matching five items, a non-numeric quantity, an unparseable date — each exits 2 with a message addressed to the person who filed the issue. The workflow posts it as a comment and stops; items.csv is untouched.

Verified against ten synthetic event payloads covering every error and success path, running validate.py on each result. Sample outputs:

DNeasy matches 5 items. Re-open this issue with the exact item id […]

213-F20 has no items recorded, so there's nothing to verify. If you found things there, file Add an item issues instead.

Two wiring choices to be aware of

  • The generated PR gets no separate check run. A PR opened with GITHUB_TOKEN doesn't trigger other workflows, so validate.yml won't fire on it. The workflow therefore runs validate.py itself before pushing — bad data never reaches a PR at all. Swap in a PAT if you'd rather see the check on the PR.
  • git push --force, not --force-with-lease. Each run is a fresh checkout with no remote-tracking ref for the branch, so a lease check refuses with "stale info" on the edited-issue path. Consequence: editing an issue rebuilds its inv/* branch from scratch, discarding hand-written commits there. The generated PR body says so.

Security notes, given this is public

Issue text is never interpolated into a run: block — apply_issue.py reads GITHUB_EVENT_PATH itself, and the commit message is assembled in a file rather than a heredoc. On a public repo, ${{ github.event.issue.body }} inside a shell command is a command-injection hole.

The workflow also only runs for OWNER/MEMBER/COLLABORATOR authors. Without that, anyone on the internet could open issues that spawn branches and PRs. Review gates merging either way, so this is about spam, not privilege — but it does mean a new lab member needs repo access before their forms work.

Can't be fully tested until this merges

Issue templates and their prefill only resolve from the default branch. So the forms won't render, and the site's "Add an item here" links won't work, until this is on main. Everything testable in isolation has been tested; the end-to-end path needs a real issue after merge. Filing a throwaway Add an item issue and deleting the resulting PR is the fastest smoke test.

Housekeeping

phase2-search-site is stale and safe to delete — its work is in main, and it briefly carried this commit before I moved it to a properly named branch. Say the word and I'll delete it.

🤖 Generated with Claude Code

Phase 3: adding or updating an item needs no git knowledge and takes about 30
seconds. Three issue forms (add an item, change status, verify a location) are
parsed by scripts/apply_issue.py, which edits data/items.csv and hands the
workflow a PR title and body.

Prefill instead of dropdowns. The plan called for location dropdowns generated
from locations.csv; that is dropped. 173 options is miserable on a phone, and
regenerating form YAML from data would have the repo editing its own workflow
files. Instead the site links to issues/new?template=add-item.yml&location=...
and GitHub prefills by field id, so scanning a drawer's QR sticker and tapping
"Add an item here" arrives with the location filled in. Free-text locations are
validated by the parser, which suggests close matches on a typo.

Field labels are the contract between the forms and the parser, because GitHub
renders forms as '### <label>' sections. Rename a label and the parser silently
stops seeing that field -- no error, the value just disappears. check_forms.py
runs in CI and fails on any form field the parser doesn't read, any handler
with no form, and any field missing an id (which would break prefill). It
caught exactly that: verify-location's free-text field is labelled "Anything
there that isn't listed", which no handler read, so things found but not
recorded -- the most useful output of a verification pass -- would never have
reached the PR body. Now surfaced there explicitly.

Nothing is written when input is bad. Unknown location, ambiguous item name,
non-numeric quantity, or an unparseable date exits 2 with a message addressed
to the person who filed the issue; the workflow posts it as a comment and
stops. Verified against ten synthetic event payloads covering every error and
success path, with validate.py run on each result.

Two deliberate wiring choices:

- The generated PR gets no separate check run, because a PR opened with
  GITHUB_TOKEN does not trigger other workflows. So the workflow runs
  validate.py itself before pushing -- bad data never reaches a PR at all.
- git push --force, not --force-with-lease: each run is a fresh checkout with
  no remote-tracking ref for the branch, so a lease check refuses with "stale
  info" on the edited-issue path. The generated PR body warns that editing the
  issue rebuilds the branch.

Issue text is never interpolated into a run: block -- apply_issue.py reads
GITHUB_EVENT_PATH itself, and the commit message is assembled in a file rather
than a heredoc. On a public repo, ${{ github.event.issue.body }} in a shell
command is a command-injection hole. The workflow also only runs for
OWNER/MEMBER/COLLABORATOR authors, so strangers cannot spawn branches and PRs;
review gates merging either way, so that check is about spam, not privilege.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@sr320
sr320 requested a review from kubu4 as a code owner August 3, 2026 19:00
@sr320
sr320 merged commit dea3305 into main Aug 3, 2026
1 check passed
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