Repository navigation
ci: link PRs to issues and sync merged work to Linear releases - #14141
Conversation
Evaluates each pull request for a Linear issue ID (title, description, or branch name) or a GitHub issue reference, and verifies the referenced issue exists. Advisory today: the failure message carries the date the check is planned to become required. Bot authors are exempt and API outages fail open. Signed-off-by: Dan Gil <dagil@nvidia.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Enterprise Run ID: 📒 Files selected for processing (6)
Included review availability: Your plan provides up to 12 included reviews per hour; 11 remain after this review. WalkthroughThe change adds pull-request issue validation, issue-first development guidance, and a dry-run workflow that synchronizes merged issues with a Linear release. ChangesIssue-linked automation
Estimated code review effort: 4 (Complex) | ~45 minutes Merge Risk: ⚪ Minimal · up to The PR adds advisory issue-link validation and a non-mutating Linear release synchronization workflow. No actionable merge-blocking risk remains after normal checks and review. 🚥 Pre-merge checks | ✅ 2 | ❌ 3❌ Failed checks (3 warnings)
✅ Passed checks (2 passed)
Full details: Linked Issues checkExplanation The PR addresses the requirements in issue Full details: Out of Scope Changes checkExplanation The PR includes the requested issue-link workflow and supporting documentation. The Linear release-sync workflow and .agents/skills/.lease.json are not required by the linked issue requirements and add unrelated scope or generated metadata. Full details: Docstring CoverageExplanation Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 6 functions across 1 files. (5 skipped: 5 unsupported.)
Comment |
On each push to main or a release branch, scan the new commits for Linear issue identifiers and PR references and attach the referenced issues to the matching release in Linear, keeping the Release view a live feature list. Ships in dry-run for the rollout; flip after one observed run confirms it targets the existing release entry. Signed-off-by: Dan Gil <dagil@nvidia.com>
Non-closing references (Part of #123) already counted; now org-scoped cross-repo references and full issue URLs count too, covering DEP and contribution-request issues in sibling repositories. Cross-repo lookups the workflow token cannot see fail open instead of counting as missing. Signed-off-by: Dan Gil <dagil@nvidia.com>
Teaches agents the issue-first workflow: start from a tracked issue, carry the ID in the branch name, reference the issue from the PR (closing or non-closing form), and remediate a failing PR Issue Link check. Signed-off-by: Dan Gil <dagil@nvidia.com>
Signed-off-by: Dan Gil <dagil@nvidia.com>
Fail open on transport and response-decoding failures, not just HTTP errors. Cap authenticated lookups from PR-controlled text at ten candidates and stop after the first verified issue. Skip Linear lookups for fork PRs so the check is not an existence oracle for guessed identifiers; fork references pass as present-but-unverified. Reword the BLOCKING_DATE comment to say where the date is used. Signed-off-by: Dan Gil <dagil@nvidia.com>
Signed-off-by: Dan Gil <dagil@nvidia.com>
Signed-off-by: Dan Gil <dagil@nvidia.com>
Signed-off-by: Dan Gil <dagil@nvidia.com>
Signed-off-by: Dan Gil <dagil@nvidia.com>
Signed-off-by: Dan Gil <dagil@nvidia.com>
Signed-off-by: Dan Gil <dagil@nvidia.com>
dmitry-tokarev-nv
left a comment
There was a problem hiding this comment.
Approved at bf22060b90.
The delta from f88aa7e83c is one line. .github/workflows/pr_issue_link.py changes them. to the rest. in the unchecked-reference message. The other seven files are byte-identical by blob SHA. The merge base is still 1beeff4402, taken from the compare API.
.github/workflows/linear-release.yml is still not in this pull request. Its blob at bf22060b90 is 10aab3fb52, and the blob on origin/main is the same. The SHA-256 is still 7b8d09969efaf2cd98c3ec29521c4461a4353ff24ff6144dd6d4dbbfc6c3fd4c, unchanged across all seven revisions. The deferred finding on that file stays deferred and is not live again.
What I re-measured on the head tree, with the pre-fix tree at 0c839eb as the discriminator:
- Eleven absent same-org cross-repository references: exit 1 at
bf22060b90, exit 0 at0c839eb. - Eleven Linear-shaped tokens on an outside-org fork: exit 1 with zero lookups at
bf22060b90, exit 0 at0c839eb. - Controls, which behave the same on both trees: ten absent references give exit 1, eleven references under a transport outage give exit 0, ten references under a transport outage give exit 0, and one existing issue gives exit 0.
- A proposal alone: exit 1 at
bf22060b90, exit 0 at0c839eb. - Echo bound: a description that carries eleven invented repository names of 9000 characters each produces 687 bytes of output at
bf22060b90. The same input grows the output at0c839eb. - Backtracking: the bounded pattern takes 0.78 ms on a pathological input. The pre-bound pattern takes 114 ms on the same input.
- A closing reference placed behind twenty proposal references still spends the first lookup on the closing one, and the check passes.
- Blocking-date backstop: bumping the workflow alone fails
test_the_template_and_the_workflow_name_the_same_blocking_date. Bumping the template alone fails it. Bumping both passes. Restoring both passes.
Over-rejection sweep between f88aa7e83c and bf22060b90: 4000 request bodies over every keyword pair, proposal counts of 5, 9, 10, 11 and 15, and every combination of which issue exists and which carries the label. Zero cases passed before and failed after. Zero cases failed before and passed after. Lookup counts are equal in every case. The same sweep against 0c839eb reports 2744 differences, so the sweep discriminates.
CI: I read the job log for Actions run 35119109874 instead of the check mark. It reports collected 37 items and 37 passed. The per-test result lines match run 35115452012 at f88aa7e83c exactly. No test was added, removed or renamed, so nothing was deleted to keep the suite green.
Deferred by decision, not by oversight: the pronoun in the unchecked-reference message is not pinned by a test. Restoring them. on the current tree leaves all 37 tests passing. The number agreement on reference and references is pinned.
No P0 and no P1 outstanding. No new P2 or P3. The round-6 P3 is fixed and verified.
The count agreed in number because a test asserted it. The pronoun did not, so putting "them" back kept CI green, and that is the fault that already came back once. The test now parametrizes both count forms and asserts the clause that carries the pronoun. Mutation-tested: restoring "them" fails both cases. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Dan Gil <dagil@nvidia.com>
dmitry-tokarev-nv
left a comment
There was a problem hiding this comment.
Approving at ec406685d. My earlier approval was attached to bf22060b90, so it no longer covers the head. This one does.
Round 8 scope: one commit, ec406685d, one file, .github/workflows/test_pr_issue_link.py, +21 and -5. I compared against the merge base 1beeff4402 as well as against bf22060b90. Both forms agree, and the other seven files in this pull request are byte-identical by blob SHA.
A test-only commit is the shape I trust least, so I measured it instead of reading it. The mutation result and the removed-line analysis are on the pr_issue_link.py:385 thread. In short: reverting only the pronoun to them. fails both new parameters and exits 1. Restoring it passes both and exits 0. The one removed assertion is reproduced exactly by the count=11 parameter. The Unit tests job log goes from collected 37 items to collected 38 items with no test added, removed, or renamed beyond that one parametrization.
I re-ran the earlier closed findings against this head. Every probe still discriminates, and every probe has a control.
| Probe | pre-fix tree | at ec406685d |
control |
|---|---|---|---|
| Cross-repo reference the token cannot see | d047b4478e: exit 0 |
exit 1, and it names ai-dynamo/enhancements#12 |
the same reference visible and existing: exit 0 on both trees |
| Linear identifier from an outside-org fork | a6f200a293: exit 0, "present but unverified" |
exit 1, zero Linear lookups | org member on the same fork: exit 0. Outside-org author on the base repository: exit 0 |
| Bounded echo of caller-supplied repository text | 0c839eb6bd: exit 0, 1 lookup, longest output line 9049 characters |
exit 1, zero lookups, longest output line 91 characters | a 100-character segment: exit 0 on both. A 101-character segment: exit 1 at head only, so the bound is exact |
CROSS_REPO_RE backtracking |
0c839eb6bd: 32.7 ms at n=2000, 130.2 ms at n=4000 |
0.31 ms and 0.59 ms | ai-dynamo/dynamo#12 still matches on both trees |
| Blocking-date backstop | the agreement test does not exist at d4912a967d^ |
workflow 10-15 with template 10-15 passes, 10-15 with 11-30 fails, 11-30 with 10-15 fails, 11-30 with 11-30 passes | the second blocking-date test passes in all four directions |
.github/workflows/linear-release.yml did not return. Its blob at this head is identical to origin/main. The SHA-256 is 7b8d09969efaf2cd98c3ec29521c4461a4353ff24ff6144dd6d4dbbfc6c3fd4c, the value recorded at every revision of this pull request so far.
Zero findings this round. Nothing deferred. I did not author any commit on this pull request.
Two weeks of advisory failures rather than four. The date now falls before the v1.6.0 code freeze, so the check first governs the freeze week; the workflow comment records that, and records that a cherry-pick citing only the pull request it picks does not satisfy the check. The script default was a third copy of the date that no test pinned. Extend the coupling test to cover it, so the script cannot announce a date the workflow never chose. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Dan Gil <dagil@nvidia.com>
A branch cut for a release takes cherry-picks, and a cherry-pick cites the pull request it picks rather than an issue. The issue link belongs on the original change, which was already checked on its way into the trunk, so a pick has nothing left to prove and the check would only produce a red mark nobody can clear. The base-branch filter on the trigger is the whole mechanism: the workflow never starts for those pull requests, so there is no check to explain away and none to mark required by accident. Nothing in the script enforces it, so a test pins it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Dan Gil <dagil@nvidia.com>
dmitry-tokarev-nv
left a comment
There was a problem hiding this comment.
Approving at 0f9d49bc59. My previous approval was attached to ec406685df, so it no longer covered the head. This one does.
The push shape is an append, not a rewrite. ec406685df is still an ancestor of the head, and the merge base with main is 1beeff4402 both before and after, so I read ec406685df..HEAD directly and did not need a diff of diffs.
Two new commits, four files, +45 and -17. They move the blocking date from 2026-10-15 to 2026-10-01, add a third copy of that date to the backstop test, and add a base branch filter so the check starts only for pull requests that target main.
One new finding, P3, on the release sync gate. It is on the linear-release-sync.yml thread. Nothing else outstanding, and nothing deferred. I did not author any commit on this pull request.
The two new tests, mutated in both directions
Both new assertions fail on the code that preceded them and pass on the code that follows them.
| Mutation | Result |
|---|---|
| none, at the head | test_the_check_runs_only_against_the_trunk passes |
remove branches: [main] from the trigger |
that test fails |
| restore it | that test passes |
set the script default date to the old 2026-10-15 |
test_the_template_and_the_workflow_name_the_same_blocking_date fails |
| restore the default | it passes |
The whole file is 39 passed at the head, which matches the count the description claims.
The matcher, run case by case against a programmed API
I ran main() for each case rather than reading the patterns.
| Case | Exit | Lookups spent |
|---|---|---|
| no reference at all | 1 | none |
| several references, closing form last | 0 | 1, and it is the closing one |
| reference to a closed issue | 0 | 1 |
| reference to an issue that does not exist | 1 | 1 |
| reference inside a fenced code block | 1 when the issue is absent, 0 when it exists | 1 |
| reference inside a quoted reply | 0 when the issue exists | 1 |
#abc, DYN- with no number, eight digits |
1 | none |
issue number 0 |
1 | 1 |
| reference that resolves to a pull request | 1 | 1 |
| reference only inside an HTML comment | 1 | none |
| cross-repository reference outside the org | 1 | none |
| identifier in the branch name only | 0 | 1 tracker lookup |
The code block and quoted reply cases pass when the issue they name really exists. That matches how the platform itself reads a closing keyword, so I did not raise it.
Failure modes, measured
| Condition | Result |
|---|---|
| tracker lookup fails | exit 0, printed as present but unverified |
| GitHub lookup fails | exit 0, printed as present but unverified |
| the tracker key is absent | same, reported in the summary rather than silent |
| eleven references, all decided as missing | exit 1, exactly 10 lookups |
| eleven references, all failing | exit 0, exactly 10 lookups |
There is no retry anywhere. Each call has a 20 second timeout and each side is capped at 10 lookups, so the worst case is bounded. The release sync step runs under set -euo pipefail with a 60 second command timeout, so an unreachable tracker turns that job red rather than passing silently.
Earlier findings, re-run against this head
| Probe | At 0f9d49bc59 |
Control |
|---|---|---|
| cross-repository reference the token cannot see | exit 1, after one repository visibility call | the same reference visible and present: exit 0, no visibility call |
| tracker identifier from a fork outside the org | exit 1, zero tracker lookups | org member on the same fork: exit 0, one lookup |
| bounded echo of caller supplied repository text | a 9000 character segment: exit 1, zero lookups, longest output line 91 characters | a 100 character segment behaves the same way |
| backtracking in the cross-repository pattern | 0.05 ms at n=2000 and 0.09 ms at n=4000 | ai-dynamo/dynamo#12 still matches |
| the blocking date reaches the failure message | the message names 2026-10-01 |
the three file agreement test fails when any copy drifts |
All 31 review threads on this pull request are resolved, and every fix they asked for still holds.
Credentials, trigger, and what each workflow writes
The issue link check runs on pull_request_target, which is the shape worth checking. It holds up. The job takes contents: read, issues: read and pull-requests: read, so it cannot comment, label, or edit anything. The checkout takes the base repository, and the only run: line is python3 .github/workflows/pr_issue_link.py, so fork code never executes. The title, body, branch name, author and repository all arrive as environment variables and never enter a shell line or a URL path without a bound. The repository segment pattern caps each segment at 100 characters, which is what keeps caller text out of the lookup URL and out of the step summary.
The release sync runs on push to main, which an outside contributor reaches only through a merged pull request. It takes contents: read and the tracker access key. It cannot write to this repository either.
Write paths, and the direction of each:
| Path | What it carries | Direction |
|---|---|---|
| check status on the pull request | pass or fail only | none |
| job step summary | the reference text the author wrote, capped | none |
| tracker release membership | identifiers read out of merged commits | repository to tracker |
| release sync job log | the scan result | tracker to repository, and this is the P3 above |
Nothing reads a title or a description back from the tracker. The check queries the identifier field alone, and the line it prints repeats the identifier the author already wrote in the pull request.
The action runs the CLI with --json and echoes the result unconditionally, and on the real path that result carries the tracker release URL. Dry run returns null, so nothing is disclosed today, and the log_level input does not suppress it because the echo sits outside the CLI's own logging. This repository is public, so its job logs are public. Nothing to fix while the gate holds. Recording it beside the gate so the pull request that flips it accepts the disclosure deliberately rather than discovering it in a public log afterwards. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Dan Gil <dagil@nvidia.com>
dmitry-tokarev-nv
left a comment
There was a problem hiding this comment.
Re-approving at 419add704c23121ede51ed5a01b1d77967a6d2e8.
The push since my round 2 approval is append only. 0f9d49bc59 is an ancestor of the new head, git log shows one commit between them, and repos/ai-dynamo/dynamo/compare reports ahead_by 1, behind_by 0. The merge base with main is 1beeff4402 at both heads, so the base did not move and no diff of diffs was needed.
The one new commit is comment only. It records, next to the rollout gate, what flipping that gate would publish. I re-read all four parts of the gate at the live head and they are unchanged: dry_run: true, the action pin 53ad0f8 (v0.17.2), no cli_version input, and no log_level input. I also checked the comment's own claims against the pinned action and the CLI version that actually runs, v0.17.2, and they hold, including the line number. Detail is on the thread.
No findings open. The matcher code did not change, so I did not re-run it: .github/workflows/pr_issue_link.py and its test file are identical by blob SHA to the tree I approved in round 2, as are the other five files.
How the push shape and the file identity were established
$ git merge-base --is-ancestor 0f9d49bc595e 419add704c23 -> YES
$ git log --oneline 0f9d49bc595e..419add704c23
419add704c ci: record what flipping the release-sync gate discloses
$ git merge-base origin/main 0f9d49bc595e -> 1beeff440293d4eea677ac20d17ac50def7fbab3
$ git merge-base origin/main 419add704c23 -> 1beeff440293d4eea677ac20d17ac50def7fbab3
$ git rev-parse --is-shallow-repository -> false
Per-file blob SHA, 0f9d49bc595e against 419add704c23:
| file | state |
|---|---|
.agents/skills/issue-first/SKILL.md |
identical |
.github/pull_request_template.md |
identical |
.github/workflows/linear-release-sync.yml |
changed |
.github/workflows/pr-issue-link.yml |
identical |
.github/workflows/pr_issue_link.py |
identical |
.github/workflows/test_pr_issue_link.py |
identical |
.gitignore |
identical |
AGENTS.md |
identical |
The new commit adds nine comment lines inside the with: block of the sync step. It writes nothing, adds no credential, and interpolates nothing. The job still holds contents: read, still takes its inputs from repository env values only, and still turns red if the tracker cannot be reached.
The word appears nowhere else in the repository, so it reads as a term of art that has to be decoded. The branch has a name. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Dan Gil <dagil@nvidia.com>
dmitry-tokarev-nv
left a comment
There was a problem hiding this comment.
Approving 7a8e393. The head moved one commit since the last approval at 419add7, so this re-reads the change at the new head rather than carrying the old approval forward.
What moved, and what I measured at the new head
Push shape. A fast-forward of one commit, established four ways rather than by reading commit ids: git merge-base --is-ancestor 419add7 HEAD succeeds; git log 419add7..HEAD is a single line; git rev-parse --is-shallow-repository is false, so that ancestry is trustworthy; and the compare endpoint reports ahead 1, behind 0. The merge base is 1beeff4 both before and after, so the base did not move.
What the commit changed. Comments and a docstring only, plus one test renamed from test_the_check_runs_only_against_the_trunk to test_the_check_runs_only_against_main. Parsing both copies of pr-issue-link.yml gives equal structures, and the same comparison against a copy with branches: [main] altered gives unequal, so the equality is a real result and not an artifact. Nothing else in the pull request changed: the other six files are the same blobs as before.
The rollout gate. Still closed at the live head. Details are on the thread at linear-release-sync.yml:84.
The matcher. pr_issue_link.py is the same blob as at 419add7, so the fifteen cases from the earlier round still hold and I did not run them again. I did run the unit tests at the new head: 39 passed, which is the earlier 38 plus the renamed one. No other file names the old test.
Carried forward. Both jobs stay read-only toward the repository, fork code still cannot execute because the checkout takes the base repository, no pull request text reaches a shell line or a URL without a length bound, and an unreachable tracker still turns the job red rather than silently passing. This push touches none of that.
No open findings. The count is zero P0, zero P1, and zero P2 or P3.
"Pick" as a bare noun is in-house shorthand, and it sat next to "the pull request it picks", which repeated the word as a verb in the same clause. Name the original pull request instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Dan Gil <dagil@nvidia.com>
dmitry-tokarev-nv
left a comment
There was a problem hiding this comment.
Re-approving at 634eb73d49cc9955a9b7c505561690a7ebe1bff8. The one new commit changes only a YAML comment and a test docstring. No code changed.
What I checked at this head
Push shape. 7a8e393 is an ancestor of 634eb73, the range 7a8e393..634eb73 holds 1 commit, the reverse range holds 0, the clone is not shallow, and the compare endpoint reports ahead 1 / behind 0. The merge base with main is 1beeff440293d4eea677ac20d17ac50def7fbab3 for both the old and the new head, so the base did not move.
What the new commit changed. pr-issue-link.yml lines 26-27 (a trigger comment) and test_pr_issue_link.py lines 441-446 (a docstring). It replaces the bare word "pick" with "cherry-pick" and "the original pull request". Both files still parse: PyYAML loads the workflow, and the 39 unit tests pass under the same flags CI uses (--confcutdir=.github/workflows --override-ini="addopts=" --override-ini="filterwarnings="). The trigger block is unchanged: pull_request_target still filters branches: [main].
The release-sync disclosure gate, all four parts, re-read at this head. Still future-only, not live.
| part | value at this head |
|---|---|
| dry-run setting | dry_run: true, linear-release-sync.yml:84 |
| action pin | 53ad0f863963e7f8e270fba18426bbb55ef55384 (v0.17.2), not repinned; the file blob is byte-identical to the last approved head |
| tool version | action default cli_version: v0.17.2; the workflow does not override it |
| logging option | log_level is not set, and it cannot help: it only adds --quiet/--verbose to the CLI, while run.sh:95 runs a bare echo "$output" outside the CLI's own logging |
I re-read run.sh at the pinned SHA rather than trusting the earlier round.
Matcher. pr_issue_link.py is byte-identical between the two heads (blob cd3f59e508ba426bd8e1572a0f39c665bb20e987), so I relied on identity and did not re-run the 15 matcher cases.
Threads. 32 review threads, all resolved. No new comments since the last round.
No new findings. Zero P0, zero P1, zero P2, zero P3.
The old date is tomorrow and this has not merged, which would leave the check announcing a deadline it never gave anyone a runway for. The new one falls after the v1.6.0 code freeze, so the check stays advisory for all of 1.6 and first governs what lands for 1.7. The comment recorded the opposite while the date sat before the freeze; it now matches. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Dan Gil <dagil@nvidia.com>
dmitry-tokarev-nv
left a comment
There was a problem hiding this comment.
Approving 50c8be618d51fee31cd14e0c94900b68b1817971. The one new commit moves the blocking date to 2026-10-15 in all three copies and rewrites the workflow comment to match. Zero findings: no P0, P1, P2, or P3 is open.
What I ran at this head, with controls.
The new commit is a fast-forward on 634eb73d49, the head of my last approval. It changes 3 files, +7/-6, and the timeline shows no force-push.
The three copies of the date agree, and no 2026-10-01 is left in the tree:
| copy | value |
|---|---|
.github/workflows/pr-issue-link.yml:58 |
BLOCKING_DATE: "2026-10-15" |
.github/pull_request_template.md:30 |
becomes required on 2026-10-15. |
.github/workflows/pr_issue_link.py:199 |
default "2026-10-15" |
With the CI flags, 39 of 39 unit tests pass on this head, on 634eb73d49, and on the merge with main. My runs used Python 3.13.15. The Unit tests job on this head passed 39 of 39 on Python 3.12.14.
In copies of the head, I made each change below and ran the unit tests. Each date change is to 2026-10-16:
| change | result |
|---|---|
| workflow date only | 1 failed: test_the_template_and_the_workflow_name_the_same_blocking_date |
| template date only | 1 failed, the same test |
| script default only | 1 failed, the same test |
| all three dates together | 39 passed (negative control) |
branches: [main] removed |
1 failed: test_the_check_runs_only_against_main |
The script prints becomes required on 2026-10-15 with BLOCKING_DATE from the workflow and with the variable unset. At 634eb73d49, it prints 2026-10-01 in both cases.
The new comment makes two claims:
- It says that the date falls after the v1.6.0 code freeze. The tree on
mainnames no freeze date, and norelease/1.6.0branch exists yet. The only record is the history of this pull request. Commit7e66fa460agives the freeze as Oct 7 and the release as Oct 21, and the old comment gave 2026-10-07. 2026-10-15 falls after that date. The release plan in the tracker is outside this review. - It says that the base-branch filter keeps the check to pull requests into
main. Thepull_request_targettrigger filtersbranches: [main], and only that event runs thecheck-issue-linkjob. Thepull_requestevent runs only the unit tests.
The merge with main at 0c0ce6d701 has no conflict. Since 2026-09-18, one commit on main touched a file of this pull request. It is 8be4df853e (#15063), which edits the repository map in AGENTS.md, not the skill index. On the merged tree, scripts/validate_skills.py passes with 30 skills. If I remove the issue-first line from the index, it fails.
linear-release-sync.yml is the same blob as at 634eb73d49, with dry_run: true. All 32 review threads are resolved, and no person commented after my last approval.
Not run yet: NVSkills CI for the new skill, which starts only on a /nvskills-ci comment from a maintainer and is not a required check. The Require an issue link job skips on pull_request by design, and its first real run comes from main after the merge. When I read the checks, the PR workflow on pull-request/14141 was still in progress.
The v1.6.0 GA date, so the check stays advisory through the whole 1.6 cycle and first governs what lands for 1.7. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Dan Gil <dagil@nvidia.com>
⏹️ Dynamo PR CI cancelled — run 37390024698 (attempt 1) on
|
| Framework | Build |
|---|---|
| TRT-LLM | ⏹️ 1 |
| Other | Jobs |
|---|---|
| dynamo-runtime | ⏹️ 1 |
| planner | ⏹️ 1 |
Failure details
No job failed outright. The 3 cancelled jobs are image builds that each hit the 1h job timeout (The job has exceeded the maximum execution time of 1h0m0s) during Build and Push Image, all at about the same time (~00:43 UTC). That is not fail-fast or a manual cancel. The two failed gate checks only mirror these cancels.
⏹️ trtllm-runtime / Build multi-arch cuda13.1: job timeout (1h) in docker build
Job: trtllm-runtime / Build multi-arch cuda13.1 · Failed step: Build and Push Image · Logs: gh run view --job 112032781244 -R ai-dynamo/dynamo --log-failed
#155 68.74 Cache timeouts 0
##[error]The operation was canceled.
##[end-action id=build-image.build;outcome=cancelled;conclusion=cancelled;duration_ms=3588357]
The job has exceeded the maximum execution time of 1h0m0s
The buildkit build was still running (Rust compile with sccache hits) when the 60-minute job limit ran out. No build error appears before the cancel.
⏹️ planner / Build multi-arch cpu: job timeout (1h) in docker build
Job: planner / Build multi-arch cpu · Failed step: Build and Push Image · Logs: gh run view --job 112032781298 -R ai-dynamo/dynamo --log-failed
#193 [linux/amd64 planner_builder 10/23] RUN ... uv pip install --overrides /tmp/overrides.planner.txt --requirement /tmp/requirements.planner.txt ...
#193 1.489 + pytest-timeout==2.4.0
##[error]The operation was canceled.
##[end-action id=build-image.build;outcome=cancelled;conclusion=cancelled;duration_ms=3600537]
The build reached the planner uv pip install stage (step 10/23) when the job hit its 1h timeout. No error appears before the cancel.
⏹️ dynamo-runtime / image / Build multi-arch cuda13.0: job timeout (1h) in docker build
Job: dynamo-runtime / image / Build multi-arch cuda13.0 · Failed step: Build and Push Image · Logs: gh run view --job 112032781792 -R ai-dynamo/dynamo --log-failed
#132 [linux/amd64 wheel_builder_base 17/19] RUN ... git clone libvpx ... curl ffmpeg-9.0.1.tar.xz ... make ...
#145 [linux/amd64 runtime_wheel_builder 5/13] RUN ... maturin build --release --features "kv-indexer,..." ...
##[error]The operation was canceled.
##[end-action id=build-image.build;outcome=cancelled;conclusion=cancelled;duration_ms=3598383]
The job was still building the runtime wheel (maturin) and ffmpeg/libvpx stages when the 1h timeout cancelled it. No error appears before the cancel.
All three builds timed out together with no build error, which looks like slow builds or builder contention rather than a code failure. Rerunning the failed jobs is likely enough.
For agents
{"pr": 14141, "run_id": 37390024698, "run_attempt": 1, "head_sha": "642f7f8c8c4a0982ef8c5ef0817531a16c8a5980",
"failures": [
{"job": "trtllm-runtime / Build multi-arch cuda13.1", "job_id": 112032781244, "failed_step": "Build and Push Image", "signature": "job exceeded maximum execution time of 1h0m0s (cancelled)", "tests": [], "log_cmd": "gh run view --job 112032781244 -R ai-dynamo/dynamo --log-failed"},
{"job": "planner / Build multi-arch cpu", "job_id": 112032781298, "failed_step": "Build and Push Image", "signature": "job exceeded maximum execution time of 1h0m0s (cancelled)", "tests": [], "log_cmd": "gh run view --job 112032781298 -R ai-dynamo/dynamo --log-failed"},
{"job": "dynamo-runtime / image / Build multi-arch cuda13.0", "job_id": 112032781792, "failed_step": "Build and Push Image", "signature": "job exceeded maximum execution time of 1h0m0s (cancelled)", "tests": [], "log_cmd": "gh run view --job 112032781792 -R ai-dynamo/dynamo --log-failed"}
]}Posted automatically by Devin for run 37390024698. Updated on every full-CI run of this PR.
Branch identifiers and issue URLs match case-insensitively, with ASCII-only folding so lookalike letters cannot re-enter a repository segment. A lowercase Linear identifier counts behind a magic word or in the branch name; a bare lowercase token in prose still does not, so cuda-13 never spends a lookup. GitHub repository names are lowercased before dedup, so case variants are one candidate. When plain references cross the lookup bound and every checked one was decided, the failure now says how many went unchecked and that a closing form is checked first. The bound and the exit behaviour are unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Dan Gil <dagil@nvidia.com>
The title rides on the issue response the check already makes, so the summary reads "GitHub issue #123 - Fix router timeout" at no extra call. It is third-party text headed for a Markdown summary, so it is flattened to one line, bounded to 120 characters and HTML-escaped first. Linear references stay identifier-only. The Actions summary on this repository is world-readable and Linear content is internal. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Dan Gil <dagil@nvidia.com>
|
/ok to test 32706e8 |
Anyone can author a GitHub issue title and anyone can read the Actions summary, so HTML-escaping alone left a Markdown link in a title clickable. Inside a code span GitHub Markdown treats links, emphasis and HTML as literal text, and a backtick in the title becomes a straight quote so the span cannot close early. The sanitisation now sits where the summary line is built, so no path from a verifier to the summary can skip it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Dan Gil <dagil@nvidia.com>
An agent that never loads the issue-first skill still reads this section, so the rule lives here in one line and the skill keeps the detail. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Dan Gil <dagil@nvidia.com>
…-link-check Signed-off-by: Dan Gil <dagil@nvidia.com>
…-link-check Signed-off-by: Dan Gil <dagil@nvidia.com>
Closes #14140
Two workflows that together keep release tracking live without manual reconciliation: the first makes every PR carry an issue reference, the second turns merged references into release membership in Linear.
PR Issue Link (advisory check)
flowchart LR A[PR opened, edited,<br/>or synchronized] --> B[Scan title, body,<br/>branch name] B --> C{Reference found?} C -- "DYN-1234" --> D[Verify via<br/>Linear API] C -- "#123 or owner/repo#123" --> E[Verify via<br/>GitHub API] C -- none --> F[Advisory failure<br/>with instructions] D -- exists --> G[Pass] E -- is an issue --> G D -- not found --> F E -- PR number or missing --> F D -. API outage .-> G E -. API outage .-> GEvaluates every pull request for a linked issue and verifies the reference exists:
Closes DYN-1234, oruser/dyn-1234-description), matching how the Linear GitHub integration links work.Fixes #123,#123, or the full URL). PR numbers do not count; the check confirms the reference is an issue.Part of #123for an issue that outlives the PR. Org-scoped cross-repo references count too (ai-dynamo/enhancements#12), which is where the contribution requests and the older DEPs live. A cross-repo reference whose repository the workflow token cannot see is reported by name and does not satisfy the check.dep:lifecycle label every proposal carries, read off the response the check already makes. KeepPart of #<dep>and link a work issue as well.Behavior:
BLOCKING_DATEenv var). It is not a required status check until that date. That date is the v1.6.0 GA date, two weeks past the code freeze, so the check stays advisory for all of 1.6 and first governs what lands for 1.7.main. A base-branch filter on the trigger, so a branch cut for a release is out of scope entirely and the workflow never starts for the cherry-picks it takes. A pick cites the pull request it picks rather than an issue; the issue link belongs on the original change, which was already checked on its way intomain. A test pins the filter, since nothing in the script enforces it..github/pull_request_template.mdno longer offers "not linked to an issue" as a sanctioned path. It names the accepted forms, says to create an issue first when none exists, and carries the same date.Security: runs on
pull_request_targetso the Linear key is available for fork PRs, and checks out only the base repository's.github/workflows(sparse). PR-controlled text (title, body, branch) reaches the script through environment variables only and is never executed or checked out.Testing:
.github/workflows/test_pr_issue_link.py, 39 cases run by this workflow's ownUnit testsjob on any pull request that touches the check. They pin the two properties that do not read off a single branch: what becomes a candidate, since PR text is untrusted and a reference that cannot survive URL construction must never reach a lookup, and when the check may fail open, since a lookup budget is not an outage. Eight of them fail against the first version of the script. The script was also run against the live APIs for six cases - Linear ID in the body (passes), Linear ID in the branch name only (passes), GitHub issue reference (passes), a PR number instead of an issue (correctly ignored), a nonexistent Linear ID (correctly rejected), and no reference at all (advisory failure with instructions and the date).Agent Skill
Adds
.agents/skills/issue-first(indexed in AGENTS.md, mirrored at.claude/skills): teaches agents the issue-first workflow - start from a tracked issue, carry the ID in the branch name, pick the right reference form (closing for completed issues, non-closing for DEPs and tracking issues), and remediate a failing check. Agents reading this repo learn to satisfy the check before it ever fails.Linear Release Sync
flowchart LR H[Push to main] --> I[linear-release-action<br/>scans new commits] I --> J[Collect DYN ids and<br/>PR references] J --> L[Release from<br/>MAIN_RELEASE_VERSION] L --> N[Attach issues<br/>dry_run: true today]On each push to
main, linear/linear-release-action (pinned to v0.17.2) scans the new commits for Linear issue identifiers and PR references and attaches the referenced issues to the release currently in development there (oneMAIN_RELEASE_VERSIONenv var, updated at each cycle open). It runs onmainonly for the rollout: the action keeps per-pipeline scan state, sorelease/*branches join once the dry run shows how that state behaves across concurrently active branches. Release branches and tags stay with the existing.github/workflows/linear-release.yml, which this PR does not change.Ships with
dry_run: true: the first runs scan and log what would be attached without writing to Linear. Flip after one observed run confirms it targets the existing release entry rather than creating a duplicate.Secrets used: the existing
LINEAR_API_KEY(issue verification) andLINEAR_ACCESS_KEY(release sync - needs to be a Linear pipeline access key; verify before flipping dry-run off).🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Dynamo v1.6.0release in dry-run mode.Documentation