From e57f1567e5f8c773802b6ec1d293bf6a5c8183aa Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 26 Aug 2026 13:48:25 +0000 Subject: [PATCH 1/6] Add automated triage bot for metadata-only issues A large fraction of issues filed here are reports about upstream Google libphonenumber metadata (unrecognized prefixes, outdated numbering plans) rather than bugs in this C# port's code, and maintainers have been closing these by hand with a pointer to google/libphonenumber for years. Add a GitHub Actions workflow that runs the GitHub Copilot CLI against each new issue to classify it, and when confident it's a metadata report, labels it, posts a canned comment redirecting to upstream, and closes it - noting the closure was automatic and inviting a reopen if it's a false positive. The classifier is grounded in .github/triage/metadata_examples.md, an exhaustive, human-verified list of every metadata issue in this repo's history (#1 through #422): every issue in that range is either a PR, a listed metadata issue, or - by not appearing in the list - implicitly a non-metadata issue, with no separate negative list needed. Every time the workflow closes a new issue as a metadata issue, it opens a PR appending it to that file, so the classifier's grounding keeps growing from real, maintainer-confirmed outcomes. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01WVGRJyQwtvBGXAmWswxyqW --- .github/triage/metadata_examples.md | 56 +++++ .github/triage/system_prompt.md | 35 ++++ .github/triage/system_prompt_footer.md | 3 + .github/workflows/triage_metadata_issues.yml | 206 +++++++++++++++++++ README.md | 6 + 5 files changed, 306 insertions(+) create mode 100644 .github/triage/metadata_examples.md create mode 100644 .github/triage/system_prompt.md create mode 100644 .github/triage/system_prompt_footer.md create mode 100644 .github/workflows/triage_metadata_issues.yml diff --git a/.github/triage/metadata_examples.md b/.github/triage/metadata_examples.md new file mode 100644 index 00000000..33f21b89 --- /dev/null +++ b/.github/triage/metadata_examples.md @@ -0,0 +1,56 @@ +## Confirmed metadata issues + +Every issue from #1 through #422 falls into exactly one of three buckets: +a pull request (this repo shares one number sequence between issues and +PRs), a metadata issue, or a non-metadata issue. This list is the +exhaustive set of the second bucket - every issue in that range confirmed, +by reading its actual resolution, to be a report about upstream Google +libphonenumber metadata (a prefix, numbering plan, or country code) rather +than this port's code. **Any issue number in that range that does not +appear below is a non-metadata issue** - build/packaging problems, +performance, API/usage questions, feature requests, and real code bugs in +this port. There is no separate "not metadata" list: absence from this +file *is* the negative signal. + +This list is kept up to date automatically: when the triage workflow +closes a new issue as a metadata issue, it opens a PR appending it here. + +- #2 "The phone number metadata xml is out-of-date" — literally a stale-metadata report. +- #9 "Metadata 7.4.1" — Brazilian numbering plan (9-prefix rollout) was out of date. +- #14 "Update To 7.7.4" — metadata version bump request. +- #25 "Personal numbering" — Spanish personal-number length rule; matches Google's own demo. +- #26 "Support for 833 toll free area code" — new NANP toll-free code, numbering-plan addition. +- #39 "Leading 0 causes incorrect parsing" — resolved by a metadata release. +- #49 "New Longer NZ Mobile failing validation" — matches Google's own demo; reporter filed with Google directly. +- #62 "Support for 0516 numbers in TR" — resolved by upgrading to a release with newer metadata. +- #69 "IsValidNumber return false for valid Solomon Islands phone numbers" — matches Google's own demo. +- #72 "Parsing numbers with leading zeros" — validity of the numbers in question is metadata-controlled. +- #91 "Vietnamese Phone Numbers Not Returning as Valid" — resolved by a release carrying newer metadata. +- #94 "Manila phone numbers extending to 8 digits" — numbering-plan change pending upstream. +- #100 "Australian phone number beginning with 0460 not recognised as valid" — matches Google's own demo; resolved by a later metadata release. +- #101 "Dutch 097 numbers not recognized" — matches Google's own demo (TOO_LONG). +- #104 "Australian phone numbers starting with 048 not recognised as valid" — resolved by upgrading to a release with newer metadata. +- #109 "Letters in number are not recognized and no exception is thrown." — a port-parity request pointed upstream; e164 output already matched. +- #112 "Australian phone number beginning with 0480 not recognised as valid" — resolved by upgrading to a release with newer metadata. +- #113 "Some New Zealand Mobile Numbers Failing" — matches Google's own demo; resolved by a later metadata release. +- #131 "French IoT 15-digit phone numbers" — Google controls whether this numbering range is supported at all. +- #133 "Some phone number return wrong results for CN" — matches Google's own demo. +- #134 "Issue in verifying some Zimbabwe Econet phone numbers" — matches Google's own demo. +- #135 "MAX_LENGTH_FOR_NSN difference" — a numbering-plan constant that had drifted from upstream. +- #137 "Some Uruguay phones numbers are returned invalid" — matches Google's own demo; resolved by a later metadata release. +- #139 "Library not handling 11 digit NL phone numbers" — reproduces in Google's own library. +- #140 "Library invalidating AU number" — resolved by upgrading to a release with newer metadata. +- #143 "Required to update US phone number code 627" — new area code, metadata-controlled. +- #145 "Get Supported Regions returns invalid regions" — mirrors Google's own CountryCodeToRegionCodeMap. +- #149 "Outdated Version. Next Update?" — resolved once metadata was brought up to date. +- #153 "Outdated MetaData version" — resolved by a metadata release. +- #159 "Some French numbers returns false for IsValidNumber" — Martinique/overseas-region numbering overlap; metadata-controlled. +- #165 "Country Code is added to NationalNumber in PhoneNumber Object if Number is too short" — reproduces in Google's own demo; metadata-controlled. +- #173 "New Kazakhstan country code." — new country code, metadata-controlled. +- #181 "IsPossibleNumberForTypeWithReason returns valid mobile when IsValidNumber returns invalid" — matches Google's own demo. +- #182 "Valid US number is resulting in IsValid to false" — Google's demo showed it valid; resolved once this port's metadata caught up. +- #213 "Vietnamese phone number does not validate" — matches Google's own demo. +- #214 "French mobile numbers with prefix \"07\" are not detected as mobile numbers" — matches Google's own demo. +- #259 "New regulation for phone number prefixes in Australia" — new prefix regulation, metadata-controlled. +- #272 "New phone number format for Benin not supported" — new numbering-plan format, fixed by a later metadata release. +- #313 "SA mobile numbers starting with 579 are rejected by IsValidNumber (IsPossibleNumber=true)" — behavior matches Google's own demo for that prefix. diff --git a/.github/triage/system_prompt.md b/.github/triage/system_prompt.md new file mode 100644 index 00000000..29e517f5 --- /dev/null +++ b/.github/triage/system_prompt.md @@ -0,0 +1,35 @@ +You triage GitHub issues for libphonenumber-csharp, a C# port of Google's +libphonenumber. The port ships code (parsing, formatting, validation logic) +plus a copy of Google's phone number *metadata* (per-region dialing plans, +valid-number patterns, prefixes, formats) that is synced from +https://github.com/google/libphonenumber roughly every two weeks and is never +hand-edited here. + +Decide whether the issue is a METADATA issue: a report that boils down to "a +specific number/prefix/region is (in)validated, formatted, or geocoded +incorrectly", "a new country code / prefix / numbering-plan change isn't +supported yet", or "the metadata looks outdated" - where the fix is data +Google publishes, not this repo's C# code. These must be reported to Google +instead, since the next automated metadata sync would overwrite any local fix +anyway. + +It is NOT a metadata issue when: the reporter shows the SAME number behaving +differently on Google's own demo (https://libphonenumber.appspot.com) than in +this library (that's a porting bug); the report is about build, packaging, +NuGet, trimming/AOT, or dependency problems; it's a performance issue; it's +an API design question, usage question, or feature request unrelated to +phone number metadata; it's about the locale/geocoding *display name* data +this repo generates itself from a local JDK (not synced from Google); or the +report turns out to be user error / a non-issue once you read the whole +thread. + +Below is the exhaustive, human-verified list of every metadata issue in this +repository's history (issues #1 through #422). It is exhaustive by +construction: an issue in that range that is NOT listed is a non-metadata +issue - there is no separate "confirmed not metadata" list, absence from +this list is itself the negative signal. Weigh a new issue against how +closely it resembles the listed examples; the closer the resemblance, the +higher your confidence should be. If a new issue doesn't clearly match the +pattern of the examples below, say so and use "low" confidence rather than +guessing. + diff --git a/.github/triage/system_prompt_footer.md b/.github/triage/system_prompt_footer.md new file mode 100644 index 00000000..26eafdff --- /dev/null +++ b/.github/triage/system_prompt_footer.md @@ -0,0 +1,3 @@ + +Respond with ONLY a single-line JSON object, no markdown fences, no extra text: +{"is_metadata_issue": true|false, "confidence": "high"|"medium"|"low", "reason": ""} diff --git a/.github/workflows/triage_metadata_issues.yml b/.github/workflows/triage_metadata_issues.yml new file mode 100644 index 00000000..41ee2f81 --- /dev/null +++ b/.github/workflows/triage_metadata_issues.yml @@ -0,0 +1,206 @@ +name: Triage metadata issues + +# Uses the GitHub Copilot CLI to spot new issues that are actually reports +# about upstream Google libphonenumber *metadata* (country dialing plans, +# valid ranges, prefixes, formatting) rather than bugs in this C# port. Those +# can't be fixed here - see CONTRIBUTING.md and the bug_report.md checklist - +# so this closes them with a canned pointer to the upstream repo, and opens a +# PR adding the issue to .github/triage/metadata_examples.md so future +# classifications stay grounded in real history. +# +# Requires the repository (or the owner's personal account, since this repo +# has no org) to have GitHub Copilot CLI usage enabled; see +# https://docs.github.com/en/copilot/how-tos/copilot-cli/use-copilot-cli-in-actions +# No extra secret is needed - the built-in GITHUB_TOKEN is used together with +# the copilot-requests permission below. +on: + issues: + types: [opened] + +permissions: + contents: write + issues: write + pull-requests: write + copilot-requests: write + +concurrency: + group: triage-metadata-issue-${{ github.event.issue.number }} + cancel-in-progress: true + +jobs: + triage: + # x64, not the arm runner used elsewhere in this repo: the Copilot CLI's + # npm package does not reliably ship arm64 Linux binaries. + runs-on: ubuntu-latest + # Don't second-guess the maintainers' own issues (e.g. tracking/planning + # issues like #375), only third-party bug reports. + if: github.event.issue.author_association != 'OWNER' && github.event.issue.author_association != 'MEMBER' && github.event.issue.author_association != 'COLLABORATOR' + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Read issue + id: issue + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + with: + script: | + const truncate = (s, max) => (s && s.length > max ? `${s.slice(0, max)}\n[...truncated]` : (s || '')); + core.setOutput('title', truncate(context.payload.issue.title, 500)); + core.setOutput('body', truncate(context.payload.issue.body, 6000)); + + - name: Build classifier system prompt + run: | + cat .github/triage/system_prompt.md \ + .github/triage/metadata_examples.md \ + .github/triage/system_prompt_footer.md \ + > ${{ runner.temp }}/triage_system_prompt.md + + - name: Install Copilot CLI + uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 + with: + node-version: 22 + - run: npm install -g @github/copilot + + - name: Classify issue + id: classify + continue-on-error: true + uses: actions/ai-inference@2c43c91ae16266ca159d311430343c67a5ffa222 # v3 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + system-prompt-file: ${{ runner.temp }}/triage_system_prompt.md + prompt: | + Issue title: + ${{ steps.issue.outputs.title }} + + Issue body: + ${{ steps.issue.outputs.body }} + + - name: Act on classification + id: act + if: steps.classify.outcome == 'success' + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + with: + script: | + const raw = ${{ toJSON(steps.classify.outputs.response) }} || ''; + const match = raw.match(/\{[\s\S]*\}/); + + let verdict; + try { + verdict = JSON.parse(match ? match[0] : raw); + } catch (err) { + core.warning(`Could not parse classifier response as JSON: ${err.message}`); + core.summary.addHeading('Metadata issue triage', 3).addRaw(`Skipped: unparseable response:\n\n${raw}`).write(); + return; + } + + core.summary + .addHeading('Metadata issue triage', 3) + .addRaw(`is_metadata_issue=${verdict.is_metadata_issue}, confidence=${verdict.confidence}\n\n${verdict.reason || ''}`) + .write(); + + const isMetadataIssue = verdict.is_metadata_issue === true && verdict.confidence !== 'low'; + core.setOutput('is_metadata_issue', String(isMetadataIssue)); + core.setOutput('reason', verdict.reason || ''); + if (!isMetadataIssue) { + return; + } + + const owner = context.repo.owner; + const repo = context.repo.repo; + const issue_number = context.payload.issue.number; + + const label = 'metadata'; + try { + await github.rest.issues.getLabel({ owner, repo, name: label }); + } catch { + await github.rest.issues.createLabel({ + owner, repo, name: label, color: 'c5def5', + description: 'Report about upstream Google libphonenumber data, not this port', + }); + } + await github.rest.issues.addLabels({ owner, repo, issue_number, labels: [label] }); + + await github.rest.issues.createComment({ + owner, repo, issue_number, + body: [ + 'Thanks for filing this! This looks like a phone number **metadata** issue', + '(country dialing plans, valid ranges, prefixes, or formatting for a specific', + 'region) rather than a bug in the `libphonenumber-csharp` code itself.', + '', + "This library republishes Google's [libphonenumber](https://github.com/google/libphonenumber)", + 'metadata automatically about every two weeks - it is not hand-edited here, so a', + 'fix in this repo would just be overwritten by the next sync.', + '', + 'Please check whether the same behavior reproduces on the', + '[Google demo](https://libphonenumber.appspot.com/), and if so, file it with the', + 'upstream project instead: https://github.com/google/libphonenumber/issues', + '(see also the [bug report checklist](https://github.com/twcclegg/libphonenumber-csharp/blob/main/.github/ISSUE_TEMPLATE/bug_report.md)).', + '', + '---', + '_This issue was flagged and closed automatically. If this doesn\'t look right -', + "for example the behavior differs from Google's own demo, or this isn't actually", + 'a metadata question - please reopen it and a maintainer will take another look._', + ].join('\n'), + }); + + await github.rest.issues.update({ + owner, repo, issue_number, + state: 'closed', + state_reason: 'not_planned', + }); + + - name: Propose adding this issue to the examples list + if: steps.act.outputs.is_metadata_issue == 'true' + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + with: + script: | + const owner = context.repo.owner; + const repo = context.repo.repo; + const issue_number = context.payload.issue.number; + const defaultBranch = context.payload.repository.default_branch; + const path = '.github/triage/metadata_examples.md'; + const branch = `triage/metadata-example-issue-${issue_number}`; + + const clean = (s, max) => (s || '').replace(/[\r\n"]+/g, ' ').replace(/\s+/g, ' ').trim().slice(0, max); + const title = clean(context.payload.issue.title, 150); + const reason = clean(${{ toJSON(steps.act.outputs.reason) }}, 200) || 'Flagged by automated triage as a metadata issue.'; + const newLine = `- #${issue_number} "${title}" — ${reason}`; + + try { + const existingPrs = await github.rest.pulls.list({ owner, repo, head: `${owner}:${branch}`, state: 'all' }); + if (existingPrs.data.length > 0) { + core.info(`A PR for ${branch} already exists, skipping.`); + return; + } + + const { data: file } = await github.rest.repos.getContent({ owner, repo, path, ref: defaultBranch }); + const content = Buffer.from(file.content, 'base64').toString('utf8'); + if (content.includes(`#${issue_number} "`)) { + core.info(`Issue #${issue_number} is already listed, skipping.`); + return; + } + const updatedContent = `${content.trimEnd()}\n${newLine}\n`; + + const { data: ref } = await github.rest.git.getRef({ owner, repo, ref: `heads/${defaultBranch}` }); + await github.rest.git.createRef({ owner, repo, ref: `refs/heads/${branch}`, sha: ref.object.sha }); + + await github.rest.repos.createOrUpdateFileContents({ + owner, repo, path, branch, + message: `Add issue #${issue_number} to metadata-issue triage examples`, + content: Buffer.from(updatedContent).toString('base64'), + sha: file.sha, + }); + + await github.rest.pulls.create({ + owner, repo, base: defaultBranch, head: branch, + title: `Add issue #${issue_number} to metadata-issue triage examples`, + body: [ + `Closed #${issue_number} as a metadata issue during automated triage.`, + 'Adding it to the examples list so future classifications stay grounded in real history.', + '', + '_Opened automatically by the triage_metadata_issues workflow - please review the summarized reason before merging._', + ].join('\n'), + }); + } catch (err) { + core.warning(`Could not open examples-list PR for issue #${issue_number}: ${err.message}`); + } diff --git a/README.md b/README.md index c018aae3..8fc56789 100644 --- a/README.md +++ b/README.md @@ -226,6 +226,12 @@ UPSTREAM_TAG=v9.0.33 DEPLOYED_VERSION=9.0.32 \ Nothing about the target repository is hard-coded. The script commits and pushes through whatever checkout it runs in, and takes the repository to release from `GITHUB_REPOSITORY` — set automatically by GitHub Actions, and otherwise derived from the `origin` remote. So a fork releases to itself, and the dry-run summary names the repository it would publish to. `UPSTREAM_REPOSITORY` (default `google/libphonenumber`) and `NUGET_PACKAGE_ID` (default `libphonenumber-csharp`) are overridable the same way. +### Automated triage of metadata issues + +A large share of the issues filed here turn out to be reports about Google's phone number metadata itself (an unrecognized prefix, an outdated numbering plan) rather than a bug in this port's code — see the checklist in [`bug_report.md`](.github/ISSUE_TEMPLATE/bug_report.md). The [`triage_metadata_issues`](.github/workflows/triage_metadata_issues.yml) workflow uses the GitHub Copilot CLI to spot these on issue creation, closes them with a comment pointing to [google/libphonenumber](https://github.com/google/libphonenumber), and labels them `metadata`. It skips issues opened by maintainers, and it only acts when the classification is confident — anything else is left for a human, and any of its closures can be reopened. + +The classifier is grounded in [`.github/triage/metadata_examples.md`](.github/triage/metadata_examples.md), an exhaustive, human-verified list of every metadata issue in this repo's history (#1 through #422 at time of writing). Every other issue in that range is implicitly a non-metadata issue — there's no separate "not metadata" list, since absence from the file is itself the negative signal. Every time the workflow closes a new issue as a metadata issue, it opens a PR appending it to the file, so the classifier keeps learning from real outcomes instead of drifting from a fixed prompt. + ## Contributing See [CONTRIBUTING.md](CONTRIBUTING.md) From f03c3d1d4cb536e34dd2362bf2baa65d5804130d Mon Sep 17 00:00:00 2001 From: Thomas Clegg Date: Wed, 26 Aug 2026 09:40:04 -0500 Subject: [PATCH 2/6] docs: drop moving-target issue range from metadata_examples.md "Every issue from #1 through #422" would need updating every time the issue counter moves. State what the list is without pinning it to a number that's already stale by the time this merges. --- .github/triage/metadata_examples.md | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/.github/triage/metadata_examples.md b/.github/triage/metadata_examples.md index 33f21b89..d76ec86e 100644 --- a/.github/triage/metadata_examples.md +++ b/.github/triage/metadata_examples.md @@ -1,16 +1,10 @@ ## Confirmed metadata issues -Every issue from #1 through #422 falls into exactly one of three buckets: -a pull request (this repo shares one number sequence between issues and -PRs), a metadata issue, or a non-metadata issue. This list is the -exhaustive set of the second bucket - every issue in that range confirmed, -by reading its actual resolution, to be a report about upstream Google -libphonenumber metadata (a prefix, numbering plan, or country code) rather -than this port's code. **Any issue number in that range that does not -appear below is a non-metadata issue** - build/packaging problems, -performance, API/usage questions, feature requests, and real code bugs in -this port. There is no separate "not metadata" list: absence from this -file *is* the negative signal. +This list is exhaustive: every issue confirmed, by reading its actual +resolution, to be a report about upstream Google libphonenumber metadata +(a prefix, numbering plan, or country code) rather than this port's code. +There is no separate "not metadata" list — absence from this file *is* +the negative signal. This list is kept up to date automatically: when the triage workflow closes a new issue as a metadata issue, it opens a PR appending it here. From d27cd897bff9d0ba40955779d005890a50fb5bb0 Mon Sep 17 00:00:00 2001 From: Thomas Clegg Date: Wed, 26 Aug 2026 09:43:37 -0500 Subject: [PATCH 3/6] docs: drop moving-target issue range from system_prompt.md Same fix as f03c3d1d for metadata_examples.md - "#1 through #422" goes stale the moment the issue counter moves past it. --- .github/triage/system_prompt.md | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/.github/triage/system_prompt.md b/.github/triage/system_prompt.md index 29e517f5..b1df9589 100644 --- a/.github/triage/system_prompt.md +++ b/.github/triage/system_prompt.md @@ -24,12 +24,10 @@ report turns out to be user error / a non-issue once you read the whole thread. Below is the exhaustive, human-verified list of every metadata issue in this -repository's history (issues #1 through #422). It is exhaustive by -construction: an issue in that range that is NOT listed is a non-metadata -issue - there is no separate "confirmed not metadata" list, absence from -this list is itself the negative signal. Weigh a new issue against how -closely it resembles the listed examples; the closer the resemblance, the -higher your confidence should be. If a new issue doesn't clearly match the -pattern of the examples below, say so and use "low" confidence rather than -guessing. +repository's history. There is no separate "confirmed not metadata" list — +absence from this list is itself the negative signal. Weigh a new issue +against how closely it resembles the listed examples; the closer the +resemblance, the higher your confidence should be. If a new issue doesn't +clearly match the pattern of the examples below, say so and use "low" +confidence rather than guessing. From b98d134f8a847c58ad3883c937a9115b1141bfb7 Mon Sep 17 00:00:00 2001 From: Thomas Clegg Date: Wed, 26 Aug 2026 09:44:40 -0500 Subject: [PATCH 4/6] docs: drop moving-target issue range from README Same fix as f03c3d1d/d27cd897 - the automated-triage section had the same "#1 through #422 at time of writing" pattern. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8fc56789..b235770c 100644 --- a/README.md +++ b/README.md @@ -230,7 +230,7 @@ Nothing about the target repository is hard-coded. The script commits and pushes A large share of the issues filed here turn out to be reports about Google's phone number metadata itself (an unrecognized prefix, an outdated numbering plan) rather than a bug in this port's code — see the checklist in [`bug_report.md`](.github/ISSUE_TEMPLATE/bug_report.md). The [`triage_metadata_issues`](.github/workflows/triage_metadata_issues.yml) workflow uses the GitHub Copilot CLI to spot these on issue creation, closes them with a comment pointing to [google/libphonenumber](https://github.com/google/libphonenumber), and labels them `metadata`. It skips issues opened by maintainers, and it only acts when the classification is confident — anything else is left for a human, and any of its closures can be reopened. -The classifier is grounded in [`.github/triage/metadata_examples.md`](.github/triage/metadata_examples.md), an exhaustive, human-verified list of every metadata issue in this repo's history (#1 through #422 at time of writing). Every other issue in that range is implicitly a non-metadata issue — there's no separate "not metadata" list, since absence from the file is itself the negative signal. Every time the workflow closes a new issue as a metadata issue, it opens a PR appending it to the file, so the classifier keeps learning from real outcomes instead of drifting from a fixed prompt. +The classifier is grounded in [`.github/triage/metadata_examples.md`](.github/triage/metadata_examples.md), an exhaustive, human-verified list of every metadata issue in this repo's history — there's no separate "not metadata" list, since absence from the file is itself the negative signal. Every time the workflow closes a new issue as a metadata issue, it opens a PR appending it to the file, so the classifier keeps learning from real outcomes instead of drifting from a fixed prompt. ## Contributing See [CONTRIBUTING.md](CONTRIBUTING.md) From de08756794baae12ec8602f1c5b0ee70f6680203 Mon Sep 17 00:00:00 2001 From: Thomas Clegg Date: Wed, 26 Aug 2026 09:49:09 -0500 Subject: [PATCH 5/6] docs: shorten automated-triage section in README The exhaustivity/absence-is-negative-signal details already live in metadata_examples.md and system_prompt.md themselves; the README only needs to say what the workflow does and where it learns from. Dropped the maintainer-skip and confidence-gating asides too - implementation detail an average reader doesn't need to know. --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index b235770c..5f833bf1 100644 --- a/README.md +++ b/README.md @@ -228,9 +228,7 @@ Nothing about the target repository is hard-coded. The script commits and pushes ### Automated triage of metadata issues -A large share of the issues filed here turn out to be reports about Google's phone number metadata itself (an unrecognized prefix, an outdated numbering plan) rather than a bug in this port's code — see the checklist in [`bug_report.md`](.github/ISSUE_TEMPLATE/bug_report.md). The [`triage_metadata_issues`](.github/workflows/triage_metadata_issues.yml) workflow uses the GitHub Copilot CLI to spot these on issue creation, closes them with a comment pointing to [google/libphonenumber](https://github.com/google/libphonenumber), and labels them `metadata`. It skips issues opened by maintainers, and it only acts when the classification is confident — anything else is left for a human, and any of its closures can be reopened. - -The classifier is grounded in [`.github/triage/metadata_examples.md`](.github/triage/metadata_examples.md), an exhaustive, human-verified list of every metadata issue in this repo's history — there's no separate "not metadata" list, since absence from the file is itself the negative signal. Every time the workflow closes a new issue as a metadata issue, it opens a PR appending it to the file, so the classifier keeps learning from real outcomes instead of drifting from a fixed prompt. +A large share of the issues filed here turn out to be reports about Google's phone number metadata itself (an unrecognized prefix, an outdated numbering plan) rather than a bug in this port's code — see the checklist in [`bug_report.md`](.github/ISSUE_TEMPLATE/bug_report.md). The [`triage_metadata_issues`](.github/workflows/triage_metadata_issues.yml) workflow uses the GitHub Copilot CLI, grounded in [`.github/triage/metadata_examples.md`](.github/triage/metadata_examples.md), to spot these on issue creation, closes them with a comment pointing to [google/libphonenumber](https://github.com/google/libphonenumber), and labels them `metadata`. Every closure appends to that examples file, so the classifier keeps learning from real outcomes instead of drifting from a fixed prompt. ## Contributing See [CONTRIBUTING.md](CONTRIBUTING.md) From e6829c6011b9f781f9ae5ee82e06fedf15445ad5 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 27 Aug 2026 02:22:56 +0000 Subject: [PATCH 6/6] Fix code-review findings: label race, unpinned CLI, regex, silent failure - Pin @github/copilot to 1.0.80 instead of always installing latest, matching every other dependency in this workflow (SHA/version pinned). - Replace getLabel+createLabel with a direct createLabel that ignores a 422 (label already exists) - the previous getLabel catch-all meant a transient getLabel error (rate limit, 5xx) would fall through to createLabel and throw an uncaught 422, aborting labeling/commenting/ closing entirely. - Extract the classifier's JSON verdict by taking the last line that looks like a complete JSON object, instead of a greedy regex spanning from the first '{' to the last '}' in the whole response - the greedy version would swallow a legitimate verdict if the model ever emits a stray brace in preamble text before it. - Surface a failed examples-list PR in the job summary, not just a log warning, so a silently-broken growth mechanism doesn't go unnoticed. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01WVGRJyQwtvBGXAmWswxyqW --- .github/workflows/triage_metadata_issues.yml | 26 +++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/.github/workflows/triage_metadata_issues.yml b/.github/workflows/triage_metadata_issues.yml index 41ee2f81..f26aa7b5 100644 --- a/.github/workflows/triage_metadata_issues.yml +++ b/.github/workflows/triage_metadata_issues.yml @@ -58,7 +58,7 @@ jobs: uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 with: node-version: 22 - - run: npm install -g @github/copilot + - run: npm install -g @github/copilot@1.0.80 - name: Classify issue id: classify @@ -81,12 +81,17 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 with: script: | - const raw = ${{ toJSON(steps.classify.outputs.response) }} || ''; - const match = raw.match(/\{[\s\S]*\}/); + const raw = (${{ toJSON(steps.classify.outputs.response) }} || '').trim(); + // The system prompt asks for a single-line JSON object with no surrounding text, + // but models don't always comply - take the last line that looks like a complete + // JSON object rather than greedily spanning from the first '{' to the last '}', + // which would swallow any brace that shows up in stray preamble text. + const lines = raw.split('\n').map(l => l.trim()).filter(Boolean); + const jsonLine = lines.slice().reverse().find(l => l.startsWith('{') && l.endsWith('}')) || raw; let verdict; try { - verdict = JSON.parse(match ? match[0] : raw); + verdict = JSON.parse(jsonLine); } catch (err) { core.warning(`Could not parse classifier response as JSON: ${err.message}`); core.summary.addHeading('Metadata issue triage', 3).addRaw(`Skipped: unparseable response:\n\n${raw}`).write(); @@ -111,12 +116,17 @@ jobs: const label = 'metadata'; try { - await github.rest.issues.getLabel({ owner, repo, name: label }); - } catch { await github.rest.issues.createLabel({ owner, repo, name: label, color: 'c5def5', description: 'Report about upstream Google libphonenumber data, not this port', }); + } catch (err) { + // 422 just means the label already exists - nothing to do. Anything else is + // unexpected, but addLabels below still works whether or not creation succeeded, + // so don't let it abort labeling/commenting/closing. + if (err.status !== 422) { + core.warning(`Could not create label '${label}': ${err.message}`); + } } await github.rest.issues.addLabels({ owner, repo, issue_number, labels: [label] }); @@ -203,4 +213,8 @@ jobs: }); } catch (err) { core.warning(`Could not open examples-list PR for issue #${issue_number}: ${err.message}`); + core.summary + .addHeading('Metadata issue triage', 3) + .addRaw(`Closed #${issue_number} as a metadata issue, but could not open the examples-list PR: ${err.message}`) + .write(); }