create-issue's deduplicate-by-title option is silently inert. Its rate-limit pre-check reads the search rate-limit bucket but compares it against a threshold of 500, and that bucket's ceiling is 30 requests/minute — so the guard short-circuits on every invocation, for every consumer, and the dedup search never runs.
The handler fails open: it logs a warning and creates the issue anyway.
Source
actions/setup/js/create_issue.cjs (current main):
const TITLE_DEDUP_MIN_SEARCH_RATE_LIMIT_REMAINING = 500; // line 38
...
const rawRemaining = response?.data?.resources?.search?.remaining; // line 497
const remaining = Number(rawRemaining);
if (!Number.isFinite(remaining)) { ... return false; }
if (remaining <= TITLE_DEDUP_MIN_SEARCH_RATE_LIMIT_REMAINING) { // line 503
core.warning(`Skipping repo-level title dedup search for ${owner}/${repo}: search rate limit remaining is ${remaining} (threshold <= ${TITLE_DEDUP_MIN_SEARCH_RATE_LIMIT_REMAINING})`);
return true; // → skip dedup
}
Why it's unsatisfiable rather than merely strict
remaining can never exceed limit, and for resources.search the limit is 30/min (10/min for code_search). A fresh, completely unused budget still fails the check:
$ gh api /rate_limit --jq '.resources.search, .resources.code_search, .resources.core'
{"limit":30, "remaining":30, "reset":...,"used":0}
{"limit":10, "remaining":10, "reset":...,"used":0}
{"limit":5000, "remaining":4980, "reset":...,"used":20}
500 looks like a value chosen for resources.core (5000/hr) that ended up applied to resources.search.
Observed behavior
Every run logs the pair below and then creates the issue regardless — the first line makes it read as though dedup is active:
Title deduplication enabled (Levenshtein distance <= 15)
##[warning]Skipping repo-level title dedup search for OWNER/REPO:
search rate limit remaining is 30 (threshold <= 500)
Note remaining is 30 — an untouched quota, not a depleted one.
Impact
Any scheduled workflow relying on deduplicate-by-title re-files the same finding every run. I hit this on a benchmark-triage workflow that filed one finding six times across four consecutive scheduled runs before it was noticed. deduplicate-by-title is often the only dedup guard configured, since the docs present it as the mechanism for exactly this.
This is distinct from #36510 (the config not being emitted at all), which is fixed — the config now loads correctly, which is why Title deduplication enabled appears.
Suggested fix
Compare proportionally against the bucket's own ceiling rather than an absolute from a different bucket:
const TITLE_DEDUP_MIN_SEARCH_RATE_LIMIT_FRACTION = 0.2;
const { remaining, limit } = response?.data?.resources?.search ?? {};
if (Number.isFinite(remaining) && Number.isFinite(limit) &&
remaining <= limit * TITLE_DEDUP_MIN_SEARCH_RATE_LIMIT_FRACTION) { ...skip... }
This also stays correct on GHES, where instance admins can tune the ceilings. A minimal alternative is to drop the constant to something under 30, but that hardcodes a value the platform owns.
Two smaller points worth considering alongside:
- Skipping dedup is currently
core.warning, which is easy to miss in a green run. Given it silently changes correctness, core.error or surfacing it in the run summary would help.
deduplicate-by-title consumes at most TITLE_DEDUP_MAX_SEARCH_PAGES (2) requests. Gating 2 requests behind a 500-request reserve is conservative even against the right bucket.
Version: v0.82.14; also present on main at time of writing.
(Reported by Claude Code)
create-issue'sdeduplicate-by-titleoption is silently inert. Its rate-limit pre-check reads the search rate-limit bucket but compares it against a threshold of500, and that bucket's ceiling is 30 requests/minute — so the guard short-circuits on every invocation, for every consumer, and the dedup search never runs.The handler fails open: it logs a warning and creates the issue anyway.
Source
actions/setup/js/create_issue.cjs(currentmain):Why it's unsatisfiable rather than merely strict
remainingcan never exceedlimit, and forresources.searchthe limit is 30/min (10/min forcode_search). A fresh, completely unused budget still fails the check:500looks like a value chosen forresources.core(5000/hr) that ended up applied toresources.search.Observed behavior
Every run logs the pair below and then creates the issue regardless — the first line makes it read as though dedup is active:
Note
remaining is 30— an untouched quota, not a depleted one.Impact
Any scheduled workflow relying on
deduplicate-by-titlere-files the same finding every run. I hit this on a benchmark-triage workflow that filed one finding six times across four consecutive scheduled runs before it was noticed.deduplicate-by-titleis often the only dedup guard configured, since the docs present it as the mechanism for exactly this.This is distinct from #36510 (the config not being emitted at all), which is fixed — the config now loads correctly, which is why
Title deduplication enabledappears.Suggested fix
Compare proportionally against the bucket's own ceiling rather than an absolute from a different bucket:
This also stays correct on GHES, where instance admins can tune the ceilings. A minimal alternative is to drop the constant to something under 30, but that hardcodes a value the platform owns.
Two smaller points worth considering alongside:
core.warning, which is easy to miss in a green run. Given it silently changes correctness,core.erroror surfacing it in the run summary would help.deduplicate-by-titleconsumes at mostTITLE_DEDUP_MAX_SEARCH_PAGES(2) requests. Gating 2 requests behind a 500-request reserve is conservative even against the right bucket.Version:
v0.82.14; also present onmainat time of writing.(Reported by Claude Code)