Skip to content

ci: link PRs to issues and sync merged work to Linear releases - #14141

Merged
dagil-nvidia merged 46 commits into
mainfrom
dagil-nvidia/pr-issue-link-check
Oct 6, 2026
Merged

dagil-nvidia merged 46 commits into
mainfrom
dagil-nvidia/pr-issue-link-check

Conversation

@dagil-nvidia

@dagil-nvidia dagil-nvidia commented Sep 1, 2026 •

Copy link
Copy Markdown
Collaborator

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 .-> G
Loading

Evaluates every pull request for a linked issue and verifies the reference exists:

  • A Linear issue ID in the PR title, description, or branch name (Closes DYN-1234, or user/dyn-1234-description), matching how the Linear GitHub integration links work.
  • A GitHub issue reference in the title or description (Fixes #123, #123, or the full URL). PR numbers do not count; the check confirms the reference is an issue.
  • Non-closing forms count the same as closing ones: Part of #123 for 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.
  • A DEP does not satisfy the check on its own. A proposal umbrella stays open across many pull requests, so it records what the work is part of and not what one change is. The check names it, keeps looking, and goes red when nothing else answers. Detection is the dep: lifecycle label every proposal carries, read off the response the check already makes. Keep Part of #<dep> and link a work issue as well.

Behavior:

  • Advisory today. When no valid link is found, the check fails with instructions and the date it is planned to become required (2026-10-21, one BLOCKING_DATE env 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.
  • Fail open on outages. If the GitHub or Linear API cannot be reached, a present-but-unverified reference passes, so an upstream incident never blocks a PR.
  • Only pull requests targeting 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 into main. A test pins the filter, since nothing in the script enforces it.
  • Bot exemptions for dependabot, github-actions, and copy-pr-bot.
  • The template matches. .github/pull_request_template.md no 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.
  • Editing the PR description re-triggers the check.

Security: runs on pull_request_target so 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 own Unit tests job 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]
Loading

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 (one MAIN_RELEASE_VERSION env var, updated at each cycle open). It runs on main only for the rollout: the action keeps per-pipeline scan state, so release/* 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) and LINEAR_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

    • Added automated checks requiring non-draft pull requests to reference a verifiable GitHub or Linear issue.
    • Added support for issue references in pull-request metadata and branch names, including cross-repository references.
    • Added synchronization of merged-commit issues with the Linear Dynamo v1.6.0 release in dry-run mode.
  • Documentation

    • Added guidance for issue-first development, accepted references, and resolving failed issue-link checks.
    • Updated documented development skills to include the issue-first workflow.

Devin Review

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>
@dagil-nvidia
dagil-nvidia requested a review from a team as a code owner September 1, 2026 21:53
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 1, 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-02T00:56:42.206421Z 0bcaf10 Manual request
ℹ️ 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.

@github-actions github-actions Bot added ci Issues/PRs that reference CI build/test actions labels Sep 1, 2026
chatgpt-codex-connector[bot]

This comment was marked as resolved.

dynamo-review-agent[bot]

This comment was marked as resolved.

@coderabbitai

coderabbitai Bot commented Sep 1, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 877b0cc9-1a17-4de1-8a28-91b54c62eaba

📥 Commits

Reviewing files that changed from the base of the PR and between 401c669 and 0bcaf10.

📒 Files selected for processing (6)
  • .agents/skills/.lease.json
  • .agents/skills/issue-first/SKILL.md
  • .github/workflows/linear-release-sync.yml
  • .github/workflows/pr-issue-link.yml
  • .github/workflows/pr_issue_link.py
  • AGENTS.md

Included review availability: Your plan provides up to 12 included reviews per hour; 11 remain after this review.


Walkthrough

The change adds pull-request issue validation, issue-first development guidance, and a dry-run workflow that synchronizes merged issues with a Linear release.

Changes

Issue-linked automation

Layer / File(s) Summary
Reference detection and API verification
.github/workflows/pr_issue_link.py
The helper extracts GitHub and Linear references, verifies them with bounded API calls, supports cross-repository references and URLs, and handles unavailable APIs and untrusted forks.
Validation decisions and reporting
.github/workflows/pr_issue_link.py
The helper reports results to stdout and GITHUB_STEP_SUMMARY, provides remediation guidance, skips configured bots, and returns the validation status.
Pull-request workflow integration
.github/workflows/pr-issue-link.yml
The workflow runs on selected pull-request events, skips drafts, passes pull-request metadata and credentials, and uses read-only permissions.
Release synchronization and development guidance
.github/workflows/linear-release-sync.yml, .agents/skills/issue-first/SKILL.md, AGENTS.md, .agents/skills/.lease.json
The repository documents issue-first practices and adds a dry-run workflow for synchronizing merged issues with the Linear Dynamo v1.6.0 release.

Estimated code review effort: 4 (Complex) | ~45 minutes

Merge Risk: ⚪ Minimal · up to 0bcaf

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)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR addresses the requirements in issue #14140, including advisory enforcement, issue verification, bot exemptions, and fail-open behavior. It does not address issue #123, which requires passing co… Remove issue #123 from the linked issues if it was linked accidentally, or add the required dynamo-run and Python engine argument-passing implementation and tests.
Out of Scope Changes check ⚠️ Warning 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 ad… Confirm that Linear release synchronization is an approved part of this PR. Otherwise, move it to a separate PR. Remove the generated .agents/skills/.lease.json file unless the repository requires it.
Docstring Coverage ⚠️ Warning 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… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes both main changes: linking pull requests to issues and syncing merged work to Linear releases.
Description check ✅ Passed The description is detailed, includes the required related issue reference, and explains the changes and testing. It does not use all template headings, and its planned enforcement date of October 21,…
Full details: Linked Issues check

Explanation

The PR addresses the requirements in issue #14140, including advisory enforcement, issue verification, bot exemptions, and fail-open behavior. It does not address issue #123, which requires passing command-line arguments through dynamo-run to Python engines via sys.argv.

Full details: Out of Scope Changes check

Explanation

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 Coverage

Explanation

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.)

  • Fix all pre-merge checks with AI

Comment @coderabbitai help to get the list of available commands.

coderabbitai[bot]

This comment was marked as resolved.

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>
@dagil-nvidia dagil-nvidia changed the title ci: add an advisory PR issue-link check ci: link PRs to issues and sync merged work to Linear releases Sep 1, 2026
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>
@dagil-nvidia
dagil-nvidia requested review from a team as code owners September 1, 2026 22:26
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Sep 1, 2026
Signed-off-by: Dan Gil <dagil@nvidia.com>
@github-actions

github-actions Bot commented Sep 1, 2026 •

Copy link
Copy Markdown
Contributor

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>
dynamo-review-agent[bot]

This comment was marked as resolved.

dynamo-review-agent[bot]

This comment was marked as resolved.

Signed-off-by: Dan Gil <dagil@nvidia.com>
dynamo-review-agent[bot]

This comment was marked as resolved.

dynamo-review-agent[bot]

This comment was marked as resolved.

Signed-off-by: Dan Gil <dagil@nvidia.com>

@dmitry-tokarev-nv dmitry-tokarev-nv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 at 0c839eb.
  • Eleven Linear-shaped tokens on an outside-org fork: exit 1 with zero lookups at bf22060b90, exit 0 at 0c839eb.
  • 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 at 0c839eb.
  • 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 at 0c839eb.
  • 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 dmitry-tokarev-nv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

dagil-nvidia and others added 2 commits September 17, 2026 15:15
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 dmitry-tokarev-nv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

Comment thread .github/workflows/linear-release-sync.yml
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 dmitry-tokarev-nv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 dmitry-tokarev-nv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 dmitry-tokarev-nv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 dmitry-tokarev-nv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 main names no freeze date, and no release/1.6.0 branch exists yet. The only record is the history of this pull request. Commit 7e66fa460a gives 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. The pull_request_target trigger filters branches: [main], and only that event runs the check-issue-link job. The pull_request event 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>
devin-ai-integration[bot]

This comment was marked as resolved.

@saturley-hall saturley-hall left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Two features from #15507 to carry over as we consolidate the issue-link workflows here: verified issue titles in the run summary, and consistent case-insensitive identifier/URL extraction. The inline comments link to examples pinned to commit d111ced in #15507.

Comment thread .github/workflows/pr_issue_link.py
Comment thread .github/workflows/pr_issue_link.py
@devin-ai-integration

devin-ai-integration Bot commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

⏹️ Dynamo PR CI cancelled — run 37390024698 (attempt 1) on 642f7f8c8c

Gate checks: ❌ backend-status-check · ✅ deploy-status-check · ❌ dynamo-status-check

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>
devin-ai-integration[bot]

This comment was marked as resolved.

@dagil-nvidia

Copy link
Copy Markdown
Collaborator Author

/ok to test 32706e8

dagil-nvidia and others added 3 commits October 5, 2026 14:07
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>
@dagil-nvidia
dagil-nvidia enabled auto-merge (squash) October 6, 2026 00:24
…-link-check

Signed-off-by: Dan Gil <dagil@nvidia.com>
@dagil-nvidia
dagil-nvidia merged commit 3902d3b into main Oct 6, 2026
123 of 124 checks passed
@dagil-nvidia
dagil-nvidia deleted the dagil-nvidia/pr-issue-link-check branch October 6, 2026 05:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

actions ci Issues/PRs that reference CI build/test documentation Improvements or additions to documentation size/XXL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ci: require pull requests to link an issue

3 participants