Failures and hard texts¶
When a call to GitHub fails¶
Every call goes through gh api, in GitHub._gh. When it fails, the GitHubError carries what GitHub said: the HTTP status from the message of gh, gh: Not Found (HTTP 404), and the types of GraphQL's errors from GitHub's JSON, such as NOT_FOUND. Its methods decide what happens next:
| The error | What happens |
|---|---|
missing(): status 404 or 410, or the type NOT_FOUND |
the thing doesn't exist: no other issue, discussion or release, a commit that no release contains, an issue that was deleted or moved |
rate_limited(): a secondary rate limit |
GitHub refused the call before doing anything, so it runs again after a minute, a write too, up to 3 times; gh never shows the header Retry-After, and a minute is what GitHub asks for without it |
transient(): a status of 500 or more, or a broken connection, in the words of gh such as connection reset, connection refused, EOF, no such host or a timeout (NETWORK_FAILURE lists them all) |
a read runs again after 2, 4 and 8 seconds; a write doesn't, since GitHub may have done it anyway |
| any other, such as the rate limit of the hour | the error goes up to the command |
Only the places that look a thing up catch an error, and only when it is missing(). Every other error stops the command: main prints it as one escaped ::error:: and the summary lists the writes of the run before it, and the write that failed. A run again repeats nothing, because every step checks first what is done:
The sweep runs each step and each issue through isolate: a GitHubError becomes a ::warning:: and a line of SweepIncomplete, which main reports with what was done before it fails the run. The steps of the sweep check what an earlier one did: a fixed issue loses needs-info and stale and gets fixed-in-next-release before its notice, so a failed notice never leaves it waiting for an answer, a comment of the release, of the closing of an unanswered issue or of a duplicate isn't posted twice, and an issue closes before its labels are removed, so the next sweep still finds one that GitHub didn't close.
Which release closes a fixed issue. mark_fixed reads the pull requests merged in the last 14 days, MERGE_LOOKBACK, a hundred at a time: GitHub lists them by their last update, which a merge is, so the pages end with the first one that nobody updated within the lookback. A busy repository merges far more than a page in that time. The query gives the state of each issue that a pull request fixes, so a closed one, as most are by then, costs no request of the token's 1,000 an hour; the read of an open issue decides. After its labels and before its notice, mark_issue_fixed asks whether a final release published after the merge already contains the merge commit, as when the run of the merge failed, so that a read of the releases that GitHub fails loses only the notice; then close_issue_released closes the issue at once, unless another fix still waits, and a later sweep never announces a fix that a released comment came after. close_issue_released takes the fixes of an issue from closedByPullRequestsReferences, only those of this repository, merged at most MERGE_LOOKBACK and LABEL_SLACK, an hour, before its last labeled event of fixed-in-next-release (a sweep labels the issue later in its run than the start that its lookback counts from), and not before a released comment that came before its last reopened event: the release closed the issue with that fix, and the reporter reopened it. first_release skips the releases published before the last of them was merged, or before the label without a fix or when someone other than github-actions[bot] set it, and compare proves that a release contains each merge commit. Both mark_fixed and close_issue_released drop a pull request whose description denies the keyword that names the issue: denies_closing looks for a negation, or partly or partially, that governs the keyword: the last word of the clause within 80 characters before it, which a punctuation mark or a dash ends, once the fillers of DENIAL_FILLERS, such as yet, fully or on its own, are taken away, and an infinitive of DENIAL_INFINITIVES with its to, such as meant to or be able to; any other word in between, such as and, which or the only of an emphasis, belongs to another thought. mark_fixed names such an issue in the summary of the sweep and logs a notice for it. A link that a maintainer made by hand, with no keyword in the description, still counts. releases reads the newest hundred, as a beta channel publishes a pre-release for each fix.
Reading the text of issues¶
Anyone can write an issue, and every analysis reads the text of every open issue and of the closed ones that changed last, for the excerpts of the other issues. One text that kept a pattern busy for minutes would stall every analysis until its job timed out. So the time to read a text grows only with its length:
- No pattern backtracks. Each one looks at a character a bounded number of times: a form heading is one line, a tag ends on its line, and when no
>closes a tag on its line, the rest of the line is passed over in one step. - HTML comments go as GitHub hides them. A line that starts with a
<!--that never ends hides the rest of the text, as on GitHub, so instructions hidden that way don't reach the AI either. A<!--without its end within a line stays text, as GitHub shows it. - Definitions go as GitHub hides them.
strip_definitionsreads the text row by row, as GitHub does: a link reference definition starts a block and can run over the rows of its paragraph, a footnote without a reference takes its paragraph and the indented rows after it, and fenced code stays. A reference counts only outside code, indented rows, link targets, definitions and footnotes, and not after a backslash; since taking out a footnote can let a definition after it start a block, the references are counted again until the rows found stay the same. Each row is read a bounded number of times, also when titles or labels never end: the rows that one read in vain hold nothing that could end another of the same kind. - Each text keeps to its place.
render_issueputs each text in a code fence one backtick longer than its longest run of backticks, under a heading that only the script writes, so no text can pass for another comment, the maintainer or the assistant;Visibletakes out the characters that GitHub shows as nothing before the fence is chosen, so that none can split a run of backticks, and after the HTML, so that none can end an HTML comment that GitHub never ends and show the AI what it hides. The security design shows why. - Tests and fuzzing time it. The tests read texts of 64 KiB that took the earlier patterns up to hours, and compare the results with theirs on ordinary texts;
fuzz/fuzz_issue_text.pyfails when reading a crafted text takes longer than half a second.