Skip to content

feat: ship the coarse-Type fallback labels as a commented-out opt-in - #34

Merged
TzuH-Hsu merged 22 commits into
mainfrom
feat/33-coarse-type-fallback
Sep 5, 2026
Merged

feat: ship the coarse-Type fallback labels as a commented-out opt-in#34
TzuH-Hsu merged 22 commits into
mainfrom
feat/33-coarse-type-fallback

Conversation

@TzuH-Hsu

@TzuH-Hsu TzuH-Hsu commented Sep 2, 2026

Copy link
Copy Markdown
Owner

Summary

Ships type:bug / type:feature in .github/labels.yml commented out, with phase 2 enforcing single-home in both directions. Also corrects a premise that has become false — see the correction section, which is the most important part of this PR.

Stacked PR — base is feat/31-runner-labels (#32). Chain: #25#27#29#32 → this. It stacks for ADR numbering, the ledger and docs/setup/bootstrap.md; the labels work itself is independent.

Also depends behaviourally on #20. Without that fix, isFormManaged matches the whole type: namespace by prefix and strips any type:* label no form produces — so type:bug would delete itself on the next issue edit. Both must land before anyone uncomments the block. No file conflicts between them.

Related issue

Closes #33

Validation

  • L0 static — markdownlint, yamllint, offline lychee (50 OK, 0 errors), actionlint, gitleaks, check-skills, check-local-md, check-license-marker — all green
  • L1 unit — make test (placeholder)
  • L2 integration — both label states and all three phase-2 branches exercised, below
  • L3 e2e — see RISK

Both label states, because a commented block is only safe if it parses as absent and correct when activated:

State Parser sees coarse_type_fallback_declared Colours yamllint (repo config)
As shipped (commented) 14 labels, no type:bug not declared pass
Uncommented 16 labels declared all valid 6-hex pass

All three phase-2 branches. The org branch is unreachable from this account's real state, so it was driven with a stubbed gh:

Branch Result
Native types present, fallback not declared RESULT=ok, no warnings
Native types present, fallback declared WARN single-home violation… + manual step, RESULT=warn
Live run on this repo phase 2 reports the three types present

Risk / rollback

RISK: L3 not run — the 404 branch cannot be exercised on this repository any more
(see the correction below: it now HAS native issue types). It was reached instead
by reading the declared set directly, and the org branch by stubbing gh. A repo
whose issue-types endpoint genuinely 404s should be checked in the scratch-repo
E2E if one can be found; if not, this is the branch to eyeball in review.
Rollback: revert this commit. The labels ship commented out, so reverting removes
a comment block, a read-only phase-2 check, and documentation — no live label or
repository state is touched either way.

Checklist

  • Conventional Commit PR title
  • Linked issue using "Closes #N"
  • No secrets, no *.local.md files committed
  • Documentation updated where affected

Correction: "native issue types are organization-only" is no longer true

I found this while testing phase 2 and it changes the framing of the whole feature, so please read it before the rest.

gh api users/TzuH-Hsu --jq .type                    -> "User"
gh api repos/TzuH-Hsu/github-project-os --jq .owner.type -> "User"
gh api repos/TzuH-Hsu/github-project-os/issue-types -> Task, Bug, Feature
                                                       created_at 2026-08-21

This is a personal account, and it has native issue types. GitHub has rolled them out to user accounts since these docs were written. The original field report was accurate when it was filed — it just describes a GitHub limitation that has since been lifted.

Six places asserted the old behaviour as fact:

File Claimed
scripts/bootstrap.sh phase 2 "native issue types are an ORGANIZATION-only GitHub feature" / "on a PERSONAL account: this feature does not exist"
.github/PROJECT_FIELDS.md "Native issue types require an organization"
.github/labels.yml (new block) "ORGANIZATION-only GitHub feature"
docs/setup/bootstrap.md §2 "an organization account is required — personal accounts don't expose native issue types"
docs/setup/bootstrap.md troubleshooting "organization-account feature"
docs/adr/ADR-0006 (new) same, in Context

All six now say to check the endpoint rather than infer from account type, which is the condition that actually determines behaviour. The PROJECT_FIELDS.md section is renamed from "Personal accounts" to "When native issue types are unavailable" for the same reason.

The fallback is still worth shipping — the endpoint can still be unavailable on some accounts and plans, and the single-home enforcement is valuable regardless — but it is no longer a personal-account story, and shipping docs asserting a falsehood would have been worse than not shipping the feature.

Why commented out, not active

The field report asked for active. Commented is correct on mechanism:

A repo with native issue types and an active type:bug label has two homes for coarse Type — the dual-write ADR-0003 forbids. And that adopter cannot undo it by deleting the label, because the next scripts/bootstrap.sh sync recreates anything labels.yml still declares. So the adopter edits labels.yml either way; the edit should fall on whoever wants the label, not on whoever must not have it.

This still answers the original complaint. The friction was "adopters must supply the colours, descriptions and the single-home explanation themselves" — the commented block ships all three, correctly placed, in the file adopters are already told to edit. Uncommenting is one edit, and it is the moment the adopter consciously picks one home.

It also keeps skills/labels-and-taxonomy rule 3's "this template ships 14 labels" literally true — verified by the parser.

Rejected alternative, on mechanism not principle

Having phase 2 create the labels on the 404 path oscillates: main() runs phase_labels before phase_issue_types, so the next run's phase 1 sees an undeclared label and prunes it (the default answer is yes), phase 2 recreates it, forever. Avoiding that needs a second autorelease:*-style prune exclusion for a class of labels no file declares, and it breaks labels.yml's single-source-of-truth claim. Recorded in ADR-0006.

Deliberately out of scope

Applying the labels automatically. The coarse Type comes from each form's top-level type: key, which never appears in context.payload.issue.body, and context.payload.issue.type is null when the feature is unavailable. The only body-derived signal is heading-sniffing, which breaks the first time an adopter renames a form field — and adopters are explicitly told to customise these forms. Doing it properly needs a form-identity marker in all three forms plus labeler logic. Separate issue.

ALLOWED_SUBTYPES is deliberately untouched: adding bug/feature there would let a crafted ### Subtype section in an untrusted issue body mint type:bug on a Task, which is exactly what that hardcoded allowlist exists to prevent.

TzuH-Hsu and others added 6 commits September 2, 2026 12:05
…efaults

Each default contradicted something the repository already documents.

squash_merge_commit_title=PR_TITLE. AGENTS.md, CONTRIBUTING.md, pr-authoring
and branch-and-commit all state the PR title becomes the commit message on
main. GitHub's COMMIT_OR_PR_TITLE default makes that false whenever a PR has
exactly one commit -- visible in this repo's own log, where #7 and #9 carry
the (#N) suffix and #11, #13 and #15 do not.

squash_merge_commit_message=PR_BODY. The COMMIT_MESSAGES default concatenates
every branch commit message into the main commit body, and release-please
deliberately parses that body for further Conventional Commits and
BREAKING-CHANGE footers. Already live here: f98cbf9's body carries
"* chore: trigger CI on release PR", harmless only because chore is
release-please's hidden bucket. A branch commit reading "fix: wip" would have
produced a phantom changelog entry or an unintended bump.

allow_rebase_merge=false. The comment justifying rebase claimed release-please
merges its own PR. It does not -- ADR-0002, the release-please workflow header
and the release-management skill all require a human merge. Nor has rebase
ever been used: the history has no merge commits, all eight merged PRs were
squash-merged, and both release PRs cut their tags that way.

allow_update_branch=true. The ruleset sets
strict_required_status_checks_policy=false, so the "Update branch" button is
not offered at all without it.

Also corrects skills/release-management/SKILL.md, which instructed
`gh pr merge --merge`. allow_merge_commit=false has been set since day one, so
that command has always returned HTTP 405 -- the documented release procedure
was broken. Its parenthetical was wrong too: release-please does not merge.

Accepted cost: with PR_BODY an unmodified PR template lands verbatim in the
main commit message. BLANK was rejected -- it drops Co-authored-by trailers
and discards the RISK/rollback record. PR_BODY reduces the misparse surface
but does not eliminate it; a Conventional-Commit-shaped line in a PR body is
still parsed.

Closes #24

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Bootstrap did nothing about security settings -- grep for secret, scanning,
visibility, vulnerability or dependabot in the script returned nothing. Two
consequences. Converting a repo private to public grants ACCESS to secret
scanning but does not enable it, and push protection in particular must be
switched on explicitly, which is how a public repo ends up without it. And
.github/dependabot.yml asserts Dependabot alerts and security updates are
enabled while nothing verifies that.

The write path is bounded by cost, not by capability: the only settings this
phase ever enables are free by construction.

- Public repo: offers secret scanning and push protection, both free.
- Private or internal: never writes them, under any flag. There they need a
  paid Advanced Security / Secret Protection seat, and a setup script must not
  commit an adopter's account to a per-committer charge. It reports the state
  and emits a MANUAL step.
- Dependabot alerts and automated security fixes: free everywhere, so offered
  regardless of visibility.
- Repository visibility is never changed and never offered. Private to public
  erases stars and watchers and publishes all Actions history -- a one-way
  door, and the phase comment says so, because "detect whether the repo is
  public" invites someone to add that prompt later.

Two states the GitHub UI blurs are kept distinct. Unreadable settings mean the
token lacks admin, not that the setting is off, so security_and_analysis
coming back null degrades to warn plus MANUAL rather than reporting "disabled".
And automated-security-fixes returns {"enabled":..,"paused":..}: enabled but
paused means no fix PR ever opens, so it is reported separately.

Verified read-only before writing the phase: gh api documents the
key[subkey]=value nested syntax, so the security_and_analysis PATCH stays a
normal run_or_dry call visible under --dry-run; /vulnerability-alerts returns
204 enabled and 404 disabled; /automated-security-fixes returns enabled and
paused.

Unlike phases 3/5/8 this phase runs several independent checks, so it
accumulates a result and calls record_phase once at the end -- the pattern
phase_issue_types uses, matching run_phase's one-record-per-exit-path
contract.

Phases 6/7/8 renumber to 7/8/9 across the script and docs. Because every read
runs for real under --dry-run, bootstrap --dry-run now doubles as a zero-risk
security audit.

Closes #26

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
De-templating rewrote README, CHANGELOG and the release-please manifest and
removed docs/template/, but never touched LICENSE -- grep for "license" in
scripts/ returned nothing. So every adopted repository shipped MIT, copyright
the template author. For an open-source adopter that is a wrong copyright
line. For commissioned work it is an irrevocable written grant to the whole
world of the right to use, modify, publish, distribute, sublicense and sell,
made before payment, which removes the leverage the payment clause was built
on. Four private client repos shipped that way.

The obvious fix is itself a defect. Substantial portions of this template ship
verbatim in every adopted repo, and MIT requires its copyright and permission
notice to travel with them; rewriting LICENSE's copyright line deletes the
only copy of that notice from the repository. So attribution is unconditional
-- every answer that writes LICENSE also writes NOTICE, MIT-keep included.
That, not the prompt, is the load-bearing part of this change.

Three answers, no default. A bare Enter re-asks and a closed stdin defers,
because confirm() treats closed stdin as "take the default", which here would
mean silently shipping the template author's MIT -- the original bug with
extra steps. --yes never writes a licence: it defers and files the decision as
the first manual step, rendered above the others with a ! marker via a new
MANUAL_URGENT list. --license mit|proprietary|defer answers non-interactively.

Phase 9 runs before de-template and is not gated by --keep-template-docs:
de-template returns early for three unrelated reasons, any of which would
otherwise swallow the decision. It guards only LICENSE and NOTICE; adding
LICENSE to phase 10's guard would make de-template skip itself on every run
once phase 9 had written. LICENSE is regenerated from a seed rather than
sed-patched, because holder names legitimately contain & and /.

No docs/template/LICENSE.proprietary.example: under --yes the phase writes
nothing and files a manual step, and phase 10 then removes docs/template --
deleting the example in the very run that told the adopter to read it. The
body lives as a script constant, mirroring CHANGELOG_SEED, and in
docs/setup/licensing.md which survives de-templating.

Also adds scripts/check-license-marker.sh, wired into make check. If
TEMPLATE_COPYRIGHT_* drifts from LICENSE the phase stops recognising its own
licence and silently does nothing -- the original defect with no symptom. The
check turns that into a red build, and no-ops in adopted repos.

main is now guarded by a BASH_SOURCE test so the file can be sourced for its
functions without running. Nothing in make verify covers this script; that
guard is the only unit-test surface it has, and all nine licence paths were
exercised through it.

De-template renumbers 9 to 10. ADR-0004 records the decision and seven
rejected alternatives.

Closes #28

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
All four workflows hardcoded runs-on: ubuntu-latest, so an adopter needing a
different runner had to edit workflow YAML. That contradicted the template
twice: AGENTS.md says customize the Makefile never the workflows, and design
principle 5 promises workflow YAML stays untouched and upgradable. runs-on is
the one adopter-facing property the Makefile physically cannot own, because
GitHub resolves it when scheduling the job, before any make target exists. The
rule was unsatisfiable for it. It also broke upgrading.md's advice to bulk
cherry-pick .github/workflows/, since a forked runs-on line makes every
template update a manual merge.

One variable, not the four originally proposed: design principle 1, three of
the four jobs are trivial and identical in shape, and every extra name is
another thing to typo. A JSON array so multi-label self-hosted selection works.
Unset reproduces today's behaviour exactly.

The cost is real and was measured rather than assumed. A throwaway probe
workflow using a separate variable name, so the required ci check was never at
risk, was pushed with the value set to the tempting wrong `ubuntu-latest` --
a bare string instead of an array. Result: fromJSON fails during scheduling,
the run completes with conclusion=failure and the message "This run likely
failed because of a workflow file issue", ZERO jobs are created, and ZERO check
runs are attached to the commit. Applied to ci.yml that means the only required
check is never reported and the pull request cannot merge, with nothing in the
Actions tab naming the variable. Recovery is `gh variable delete
RUNNER_LABELS`. ci.yml's header now documents exactly that, alongside the two
existing routes into the same trap.

AGENTS.md is amended to name the exception rather than leave it as folklore,
and hygiene rule 1 is amended to match so the skill does not contradict the
shipped workflows.

New docs/setup/runners.md covers the variable, the malformed-value failure and
its recovery, the linux x86_64 bound imposed by install-ci-tools.sh, self-hosted
runner caveats that were not written down anywhere (npm and pip installs escape
INSTALL_DIR and run globally on the host every job), and why downsizing a runner
to save money is usually a pessimisation on a repo shaped like this.

ADR-0005 records the decision, and records the plain-string alternative as the
closest call: it would remove the malformed-JSON footgun entirely at the cost of
multi-label support.

Closes #31

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PROJECT_FIELDS.md already documented that type:bug / type:feature labels are
the fallback home for coarse Type when native issue types are unavailable, but
labels.yml shipped no such labels -- an adopter had to invent the names,
colours and descriptions and re-derive the single-home reasoning.

They ship COMMENTED OUT rather than active, on mechanism rather than taste. A
repo that has native issue types and an active type:bug label gives coarse Type
two homes, the dual-write ADR-0003 forbids -- and that adopter cannot undo it by
deleting the label, because the next bootstrap sync recreates anything
labels.yml still declares. The adopter edits labels.yml either way; the edit
should fall on whoever wants the label, not on whoever must not have it. It also
keeps the labels-and-taxonomy budget of 14 literally true.

Phase 2 gains enforcement in both directions, which "ship them active" cannot
provide at all: on the 404 path it reports whether the fallback is in use and
offers it; when native types ARE available and the fallback is also declared it
reports a single-home violation. It reads the declared set via parse_labels_yml
rather than the live repo, so --dry-run stays honest. It creates no labels and
still cannot fail.

Rejected alternative recorded in ADR-0006: creating the labels in phase 2 on the
404 path oscillates, because main() runs phase_labels first, so the next run
prunes the undeclared label and phase 2 recreates it, forever.

CORRECTION, found while testing this. The premise that native issue types are
organization-only is no longer true. This repository is User-owned and
repos/TzuH-Hsu/github-project-os/issue-types returns Bug/Feature/Task, created
2026-08-21 -- GitHub has rolled them out to personal accounts. Six places
asserted "ORGANIZATION-only" / "requires an organization" / "this feature does
not exist on personal accounts"; all six now say to check the endpoint rather
than infer from account type, which is the condition that actually matters. The
fallback is still needed -- the endpoint can still be unavailable -- but it is
no longer a personal-account story.

Verified both label states: as shipped the awk parser sees exactly 14 labels and
the fallback reads as not declared; uncommented it sees 16, all colours valid
6-hex, and yamllint passes with the repo config. Both phase 2 branches were
exercised, the org one via a stubbed gh since this account now has native types.

Depends on #16 / #20: without the issue-labeler prefix-match fix, type:bug would
delete itself on the next issue edit. Both must land before anyone uncomments.

Closes #33

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@TzuH-Hsu TzuH-Hsu added this to the v0.3.0 milestone Sep 2, 2026
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 2, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-05T13:06:44.766444Z d7d11d3 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ef03a77fae

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/PROJECT_FIELDS.md
Comment on lines +56 to +58
- The labels are applied **by hand**. `.github/workflows/issue-labeler.yml`
never adds or removes them: the coarse Type comes from the form's top-level
`type:` key, which is not part of the issue body the labeler parses.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Exclude fallback types from form-managed removals

When the fallback is enabled and an issue with a manually applied coarse-Type label is later edited, .github/workflows/issue-labeler.yml:119-138 classifies every type:* label as form-managed but only derives desired labels from the Task subtype field, so it removes type:bug or type:feature. The assertion here that the workflow never removes these labels is therefore false, and opted-in type metadata silently disappears; narrow the managed predicate to the subtype labels or explicitly exempt the fallback labels.

AGENTS.md reference: AGENTS.md:L9-L12

Useful? React with 👍 / 👎.

Comment thread scripts/bootstrap.sh Outdated
Comment on lines +404 to +407
coarse_type_fallback_declared() {
local labels_file=".github/labels.yml"
[ -f "$labels_file" ] || return 1
parse_labels_yml "$labels_file" | cut -f1 | grep -qxF "type:bug"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Validate both fallback labels

If an adopter accidentally uncomments only type:feature, this predicate returns false, so a repository with native types passes without warning despite having two homes for Feature; conversely, uncommenting only type:bug makes phase 2 report that both fallback labels are in use even though Feature has no fallback home. Check the presence of each label and report a partial declaration instead of treating one label as proof of the pair.

AGENTS.md reference: AGENTS.md:L9-L12

Useful? React with 👍 / 👎.

Comment thread scripts/bootstrap.sh Outdated
Comment on lines 428 to 430
warn " - personal account: GitHub rolled these out to user accounts too, so check Settings -> Issue types before assuming you cannot have them"
warn " see .github/PROJECT_FIELDS.md for the documented fallback on personal accounts"
manual "Org repos: enable/verify native Bug/Feature/Task issue types in org settings. Personal accounts: the feature does not exist — see the 'Personal accounts' note in .github/PROJECT_FIELDS.md for the fallback"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Update the stale personal-account manual step

On every 404 or empty response, the new warning tells personal-account users that native issue types may be available in Settings, but the immediately following manual message still says the feature does not exist and points to a removed "Personal accounts" heading. This gives users contradictory setup instructions in the same phase; update that manual summary to match the new endpoint-based guidance and current heading.

Useful? React with 👍 / 👎.

TzuH-Hsu and others added 9 commits September 5, 2026 15:11
1. An ampersand in the copyright holder corrupted LICENSE on bash >= 5.2.
   ${var//pat/repl} expands an unescaped & in the REPLACEMENT to the matched
   text when patsub_replacement is on, which is the default from 5.2. A holder
   of "Smith & Jones a/s" rendered as "Smith __HOLDER__ Jones a/s" while the
   phase reported success. My earlier test missed it because macOS /bin/bash is
   3.2, where the option does not exist -- and escaping as \& is itself literal
   on 3.2, so no single expansion is correct on both. Replaced with subst_all,
   a literal prefix/suffix-removal helper that never interprets the
   replacement. Verified identical output on 3.2 and 5.3.

2. write_notice returned early whenever a NOTICE file existed, treating mere
   existence as proof the upstream notice was present. An adopter with their
   own NOTICE for other dependencies would have had LICENSE replaced while the
   only copy of the template's MIT notice was silently dropped -- the exact
   violation this phase exists to prevent. It now looks for the upstream
   copyright line, appends when absent, and keeps existing content.

3. docs/setup/licensing.md's NOTICE block shipped literal __YEAR__ __HOLDER__
   placeholders, so anyone following the documented manual path produced a
   NOTICE with no attribution in it. It now carries the template's real
   copyright line, with a sentence explaining that this one is deliberately not
   the adopter's identity.

Fixing (1) introduced a regression that the doc-vs-output diff then caught:
command substitution strips trailing newlines, so LICENSE lost its final
newline and the NOTICE closing rule ran into "SOFTWARE.". Both fixed, the
constraint is documented on subst_all, and the doc block is now byte-identical
to what the script writes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Pushed the previous commit before reading the markdownlint result -- my
command chained on git add rather than gating on lint. CI would have caught
it; that is not the same as checking.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Codex review, and it is the sharpest finding in the stack. runners.md
documented how to point RUNNER_LABELS at a self-hosted runner and listed only
housekeeping caveats. But ci.yml triggers on pull_request, which on a public
repository includes fork pull requests, and the job checks out the PR's own
tree and runs make ci-tools and make ci-pr from it. Anyone on the internet
could therefore execute their own Makefile and scripts on the adopter's
machine, with its filesystem, network position and reachable credentials.
Ephemeral cleanup does not help -- the damage happens during the job.

The section now leads with the prohibition rather than burying it: on a public
repo leave RUNNER_LABELS unset; the only safe shapes are making the repo
private, or splitting the workflow so fork PRs stay on hosted runners. It also
says why "require approval for first-time contributors" is not a fix --
approval is per-contributor, not per-diff. The operational caveats are demoted
to a private-repository subsection, and ci.yml's header carries the warning at
the point of risk.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@TzuH-Hsu

TzuH-Hsu commented Sep 5, 2026

Copy link
Copy Markdown
Owner Author

Codex P1 addressed

The finding was correct for the branch state you reviewed: this chain was cut from main before the labeler fix landed, so isFormManaged still matched the whole type: namespace by prefix and would have stripped type:bug / type:feature — making the PROJECT_FIELDS.md assertion false.

That fix (#20, FORM_MANAGED_TYPES) has since merged to main. I've brought main up through the whole stack rather than assuming the merge would sort it out, and verified against the actual merged tree:

type:bug     formManaged=false  -> labeler never touches it (claim true)
type:feature formManaged=false  -> labeler never touches it (claim true)

The only remaining label.startsWith('type:') in the file is inside the comment explaining why it is not used. So the assertion now holds as written.

Bringing main up also required resolving one conflict in docs/setup/bootstrap.md — the empty-clone Troubleshooting entry versus the security-settings one. Both were kept, empty-clone first since it happens before phase 0.

The two P2s here (validating both fallback labels rather than just type:bug, and the stale manual step in the 404 branch) are not yet addressed.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c792c57528

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/bootstrap.sh Outdated
ok "label fallback in use — coarse Type home is type:bug / type:feature (see .github/PROJECT_FIELDS.md)"
else
warn " label fallback NOT in use — this repo currently has no home for coarse Type"
manual "No coarse Type home. Either accept that (subtypes, priority and area still work), or uncomment the type:bug / type:feature block in .github/labels.yml and re-run bootstrap — see 'Personal accounts' in .github/PROJECT_FIELDS.md"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Distinguish lookup failures before recommending fallback labels

When this request fails for anything other than a confirmed 404/empty result—for example, rate limiting or a transient API/network error—lines 420–422 erase the failure and this new branch tells the adopter to uncomment the fallback labels. gh help exit-codes documents exit 1 as a command that “fails for any reason,” so a repository whose native types still exist can be misdiagnosed and guided into dual-writing Type. Preserve the error/status and offer the fallback only after a confirmed unavailable response; report other errors as lookup failures.

AGENTS.md reference: AGENTS.md:L11-L11

Useful? React with 👍 / 👎.

TzuH-Hsu and others added 6 commits September 5, 2026 15:31
1. An unreadable secret-scanning state was treated as disabled. security_and
   _analysis is only populated for callers with admin on the repo, so a token
   without it yields "public\tunknown\tunknown" -- a non-empty facts line that
   fell through to the else branch and prompted to enable, or PATCHed outright
   under --yes. That acts on a guess and reports a state never observed, which
   is the opposite of the unknown-is-not-disabled rule this phase was written
   around. There is now an explicit unknown branch that warns, emits a manual
   step, and writes nothing.

2. The Actions-permission troubleshooting entry still said phase 6 after the
   renumber moved it to 7, so the recovery text pointed at the Security prompt
   instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1. LICENSE was replaced before NOTICE was known to be writable. If the NOTICE
   write then failed -- directory permissions, quota, I/O -- the repository was
   left with neither the template's original notice nor the promised
   attribution, a worse state than not running the phase at all. NOTICE is now
   written first and LICENSE is only touched once it succeeded. Verified by
   making NOTICE an unwritable path: the phase fails and LICENSE is still the
   template MIT, byte for byte.

2. The BASH_SOURCE guard covered main but not the top-level argument parser, so
   sourcing the file from a shell that had positional parameters consumed the
   caller's arguments and exit 1'd on the first one it did not recognise --
   terminating the sourcing shell. Reproduced with `set -- unexpected-arg`.
   Both the parser and main now sit behind a bootstrap_is_main helper.
   Sourcing still applies set -euo pipefail to the caller; that is documented
   rather than silently changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1. coarse_type_fallback_declared checked only type:bug, so a half-uncommented
   pair misreported in both directions: a repo with only type:feature passed
   the org single-home check silently, and a repo with only type:bug was told
   both labels were in use. Replaced with coarse_type_fallback_state, which
   echoes none / partial / both. The 404 branch now names the half-declared
   case and says to uncomment both or neither, since one label alone leaves the
   other coarse type homeless; the org branch treats partial as a violation
   too.

2. The 404 branch's manual step still said the feature does not exist on
   personal accounts and pointed at a "Personal accounts" heading that the same
   PR renamed. It contradicted the warning printed immediately above it, which
   already says to check Settings → Issue types. Rewritten to match, with the
   current heading.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@TzuH-Hsu

TzuH-Hsu commented Sep 5, 2026

Copy link
Copy Markdown
Owner Author

Codex P2s addressed

1. Partial declaration. Correct in both directions, and I had only considered one. coarse_type_fallback_state now echoes none / partial / both:

labels.yml state 404 branch org branch
both commented (shipped) none "NOT in use" ok, no violation
only type:bug partial "HALF declared" warn + violation
only type:feature partial "HALF declared" warn + violation
both uncommented both "fallback in use" warn + violation

The half-declared message says to uncomment both or neither, since one label alone leaves the other coarse type with no home.

2. Contradictory manual step. Fixed. It still said the feature does not exist on personal accounts — contradicting the warning printed immediately above it — and pointed at the "Personal accounts" heading this same PR renamed. Both corrected.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fb9d4ab049

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/labels.yml
# rolled out to personal accounts too, so check the endpoint rather than
# assuming from your account type -- bootstrap phase 2 checks it for you.
#
# Uncomment the two entries below ONLY on a personal-account repo where you

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Gate the fallback on endpoint availability

On an organization-owned repository where the issue-types endpoint is unavailable—a case docs/setup/bootstrap.md:97-99 explicitly allows—this instruction forbids enabling the only fallback even though the ADR identifies endpoint availability, not account type, as the deciding condition. Such repositories are left without a coarse-Type home; permit the labels whenever the endpoint is 404/empty and forbid them only when native types are actually returned.

AGENTS.md reference: AGENTS.md:L11-L11

Useful? React with 👍 / 👎.

@TzuH-Hsu
TzuH-Hsu changed the base branch from feat/31-runner-labels to main September 5, 2026 12:54
@TzuH-Hsu
TzuH-Hsu merged commit 4d06dc1 into main Sep 5, 2026
1 check passed
@TzuH-Hsu
TzuH-Hsu deleted the feat/33-coarse-type-fallback branch September 5, 2026 13:03
@github-actions github-actions Bot mentioned this pull request Sep 5, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d7d11d3e0e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/bootstrap.sh
Comment on lines +495 to +499
if [ "$(coarse_type_fallback_state)" != "none" ]; then
violation=1
warn "single-home violation: native issue types are available AND the type:bug/type:feature fallback is declared in .github/labels.yml"
warn " coarse Type now has two homes; they will drift (ADR-0003)"
manual "Re-comment or delete the type:bug / type:feature entries in .github/labels.yml and re-run bootstrap — with native issue types available, the native type is the single home for coarse Type"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Block fallback labels before syncing native-type repos

When an adopter uncomments the fallback on a repository where native types are available—the exact misconfiguration handled here—main() has already run phase 1 and created or updated type:bug and type:feature before this check executes. This branch only warns, so bootstrap leaves the repository with both metadata homes until the operator edits the file and runs it again; check availability before label sync or prevent those fallback labels from being created in this case.

AGENTS.md reference: AGENTS.md:L11-L11

Useful? React with 👍 / 👎.

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.

Ship the personal-account coarse-Type fallback labels as a commented-out opt-in

1 participant