Repository navigation
Close only CI's own LabVIEWs and harden the CI scripts and workflow templates - #46
Merged
Merged
Conversation
Resolve-CiEnvironment.ps1 -Fix -CleanStart passed every installed LabVIEW to Stop-LeftoverVipm. Whenever a VIPM Desktop was open at the start of a job, every running LabVIEW on the machine was force-closed, including the maintainer's development LabVIEW on the runner machine, with whatever was unsaved in it. Now the clean start runs after the test and comparison LabVIEWs have been chosen and only ever closes those two. Of these, a LabVIEW one of whose windows has a "*" in its title (LabVIEW's mark for unsaved changes) is left running, with a note in the job summary, unless the runner's environment has CI_DEDICATED_MACHINE=1. The same rule applies where else CI force-closes a LabVIEW: closing the test LabVIEW at the clean start, and Resolve-ViServerPort -Fix closing a LabVIEW that does not listen on its port (which Invoke-CliTest.ps1 and New-ViDiffReport.ps1 also call). When that LabVIEW is kept, its LabVIEW.ini is not written while it runs, and the compiled object cache of a test LabVIEW that is still running is kept. New functions in LabVIEWCi.psm1: Select-LabVIEWToStop (the decision, with no side effects), Get-ProcessWindowTitle (titles of all visible top-level windows of a process), Get-RunningLabVIEW, Stop-CiLabVIEW and Test-CiDedicatedMachine. The doc comments now say that these LabVIEWs are force-closed. tools/setup/README.md documents CI_DEDICATED_MACHINE. A test or comparison LabVIEW without unsaved changes is still closed, and VIPM Desktop is still ended at the clean start; unsaved changes in a VI whose window is not open cannot be seen from outside. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Four copies of the same code ran a console program with stderr merged into its output while keeping Windows PowerShell from stopping on the stderr lines (Invoke-CliTest.ps1, two in New-ViDiffReport.ps1, Test-InstalledPackage.ps1), and three scripts had their own list of places to look for vipm or gh. LabVIEWCi.psm1 now has Invoke-NativeCommand (returns ExitCode and Output), Get-VipmPath and Get-GhPath, and the scripts call them. Behaviour is the same, with three small differences: an empty stderr line from the VI comparison is reported as its text, as the other scripts already did; a program that is not installed returns exit code -1 with a "not recognized" line in the output; and Test-InstalledPackage.ps1 and Publish-PrReport.ps1 now also look for vipm and gh under ProgramW6432, like Resolve-CiEnvironment.ps1 did already. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LabVIEW.ini and VIPM's Settings.ini were read and written with [Text.Encoding]::Default, which is the ANSI code page only in Windows PowerShell and UTF-8 in PowerShell 7. Get-AnsiEncoding in LabVIEWCi.psm1 now names the system code page (GetEncoding(0), 1252 if that fails), and Get-IniValue reads with it too. The lines written to GITHUB_ENV, GITHUB_PATH and the job summary go through Add-GitHubFileText as UTF-8 without BOM; Add-Content wrote them in the ANSI code page, so a path with non-ASCII characters reached the next steps garbled. Test-InstalledPackage.ps1 writes its initialize request to a temporary file of its own run and deletes it at the end. Paths given by the caller are passed with -LiteralPath in Invoke-CliTest.ps1, Test-InstalledPackage.ps1, Install-Dependencies.ps1 and Resolve-CiEnvironment.ps1, so a "[" in a folder name is not read as a wildcard. Install-Dependencies.ps1 keeps a VIPM_DESKTOP_LIVELINESS_TIMEOUT that the caller has set instead of replacing it with 600 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New-ViDiffReport.ps1 ignored the exit codes of git diff, merge-base and worktree add. A failed diff looked like "no VI changed" and passed; a failed worktree add left every old version missing. The file list was parsed from quoted, tab-separated output, so a path with non-ASCII characters or quotes came back in git's escaped form and was not found. Now every git call goes through Invoke-Git (LabVIEWCi.psm1), which reads git's output as UTF-8 and throws on a non-zero exit code. The file list comes from "git -c core.quotePath=false diff -z" and is split on NUL (ConvertFrom-GitNameStatus). Stale worktrees of a killed earlier run are pruned first, and a failing clean-up only warns. Difference headings and details from LabVIEW's HTML report, frame captions and folder names are now shown as plain text (ConvertTo-MarkdownText): tags are dropped, and | < > & and @ are escaped, so "<Default>" stays visible, a "|" no longer splits a table row and "@name" in a label does not mention anyone. LabVIEW CLI output is wrapped in a code fence longer than any run of backticks in it (Format-MarkdownCodeBlock). Paths are passed with -LiteralPath. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Publish-PrReport.ps1 checked only the exit code of the push. A failed worktree add, commit or rev-parse went unnoticed; it built the report commit on a fixed temporary branch, assets-tmp, which a cancelled earlier run could leave behind and which was force-deleted afterwards; a failed comment lookup through gh was taken as "no comment yet" and posted a second report; only the first 100 comments were searched; and any comment with the marker was overwritten, whoever had written it. Now every git call goes through Invoke-Git and fails the step on an error. The report commit is built without any branch: a worktree with --no-checkout (empty index), write-tree and commit-tree, pushed by its id to pr-assets/pr-<number>. The comment lookup checks gh's exit code, reads all pages (gh api --paginate, or page by page through the REST API) and edits only a comment that carries the marker and was written by the user the token belongs to (github-actions[bot] in the workflow, whose token cannot read /user). Otherwise a new comment is posted. For checkouts without persisted credentials the push uses GH_TOKEN, sent as the same Authorization header actions/checkout would store, through GIT_CONFIG_COUNT/KEY/VALUE of that one git call: it is not on a command line and not written to a config file. A checkout that still has its own header, or a local run without GH_TOKEN, pushes as before. Invoke-Utf8Command (LabVIEWCi.psm1) runs git and gh with their output read as UTF-8. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Convert-CarayaReport.ps1 took the deepest indented lines as the asserts. A single line one level deeper than the rest moved the assert level down, and every test disappeared from the table. It also cut every assert label at its last " - ", so a label such as "fake_tool_arrays - intarray has items of type integer" was shown as "fake_tool_arrays". Labels went into the table as they were, so a "|" split the row, "<x>" vanished as an HTML tag and "@name" mentioned someone, and the protocol tester's output could close its own code fence. The report is now read by its structure, described in the script: caller lines each at their own depth, then the tests at one depth, each followed by its asserts one level deeper. The assert level is the depth most lines share, the tests are one level above it, and anything deeper belongs to the current test. Only test lines are split into label and VI. Cells go through ConvertTo-MarkdownText, the protocol output through Format-MarkdownCodeBlock, and both reports are read as UTF-8 with -LiteralPath. TestStatus and ProtocolStatus accept "not-run", which the pull request workflow passes when it skipped the step because finding LabVIEW or installing the packages failed; the report then says so instead of "no per-test report was produced". On a real report from the runner machine (64 tests) the table is the same apart from the full assert labels. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Proposed changes to the release workflow template (the maintainer copies
it to .github/workflows/release.yml):
- The build-and-release job fails for a v* tag whose commit is not an
ancestor of origin/main (git merge-base --is-ancestor), so a release is
always built from reviewed code. test-* tags, which only build a test
package, may still point anywhere. The checkout fetches the full
history for this check.
- The job runs in the "release" environment, so it waits for the approval
of its required reviewer.
- The workflow token is read-only (permissions: contents: read); only
build-and-release gets contents: write, to create the release.
- github.ref_name reaches the script through env: (TAG_NAME) instead of
being pasted into it with ${{ }}.
- actions/checkout, actions/upload-artifact and softprops/action-gh-release
are pinned to the commits of v4.4.0, v4.6.2 and v2.6.2, the newest
releases of the major versions used so far.
- The release text is %(contents:subject) and %(contents:body), so the
signature block of a signed tag is no longer part of it, and git's
output is read as UTF-8. A failed tag fetch stops the step.
- The version pattern is anchored (^v1.2.3[.4]$): a test-* tag with a
number in it no longer rewrites the package version.
- VIP_PATH is written to GITHUB_ENV as UTF-8.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Proposed changes to the pull request workflow template (the maintainer copies it to .github/workflows/pull-request.yml): - Every checkout uses persist-credentials: false, so no token is left in .git/config while the pull request's tests, VIs and protocol tester run on the self-hosted runner. Only the step that publishes the report gets the token, as GH_TOKEN; Publish-PrReport.ps1 now pushes the report images with it (commit "Check every git and gh call when publishing the review report"). - always() is replaced by !cancelled(), so a cancelled run stops instead of posting a half report. - Step outputs, the base branch, the pull request number and head commit reach the scripts through env: instead of being pasted into run: blocks; the run URL and run number come from the runner's GITHUB_* variables. - The finding, install and re-check steps have ids. Protocol tests and the VI diff check their outcomes, so they no longer run with empty LABVIEW_* values after a failed setup; the test report says "not run" for skipped steps, the comment says when the VI diff did not run, and the last step also fails and says why when one of the setup steps failed or the diff did not run. - GITHUB_OUTPUT, GITHUB_ENV and the job summary are written as UTF-8 without BOM ([IO.File]::AppendAllText); ">>" wrote UTF-16 and Add-Content the ANSI code page. - test-build has the same fork guard as review. - The actions are pinned to the same commits as in the release template. - The file ends with a newline. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…late - tools/setup/README.md: the VI Server port is taken from VIPM's settings first, then LabVIEW.ini, then the repository variable, then 3363, as Resolve-ViServerPort does; the table and the "VI Server port" section said the running LabVIEW's port came first and that CI read the port from LabVIEW.ini. The table also says now that a LabVIEW on the wrong port is force-closed, and when not. - The mass compile recipe ended with "git checkout -- .", which discards every uncommitted change in the checkout. It now restores only tests and says to check git status first. - tools/ci/README.md: the release template is described as it is (tests on every push, build with the chosen test LabVIEW rather than a fixed LabVIEW 2020 64-bit, v* tags on main only, release environment), with the environment and tag ruleset it expects. "A pull request cannot change how its own report is produced" was not true: the pull request's VIs and tester run on the same runner, and its own scripts are used while main lacks them. The section says that now, and how CI_DEDICATED_MACHINE changes what CI closes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tests✅ All 64 tests passed. Per-test results
Protocol tests✅ Protocol tester output (0 failed required checks)VI diff reportNo VI, VIM or CTL files changed in this pull request. Report for commit f354e18. The installable test package is under Artifacts of this run once the build job has finished. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Review findings on the CI tooling. The main one: with
-CleanStartCI force-closed every running LabVIEW on the runner whenever a VIPM Desktop was open. The runner is the development machine, so that included the maintainer's own LabVIEW and anything unsaved in it. Now CI closes only the test and comparison LabVIEWs it chose for the job, and none with unsaved changes. Alongside: the workflow templates get least-privilege tokens, pinned actions and a release guard, the report scripts stop ignoring git and gh failures, and LabVIEW and Caraya text is escaped in the reports. No VI is changed.*(unsaved changes) in a window title unlessCI_DEDICATED_MACHINE=1-Fix)v*tagcontents: writemain, job in thereleaseenvironment, write access only for that job.git/configwhile the PR's code runspersist-credentials: false; only the publishing step getsGH_TOKENLABVIEW_*git difflooked like "no VI changed")|,<,>,@in labels and LabVIEW textsChanges
All files are PowerShell scripts, the two workflow templates and docs.
.github/workflows/is not touched.tools/ci/LabVIEWCi.psm1(modified)Select-LabVIEWToStopdecides which running LabVIEWs may be closed, with no side effects: only paths the job uses, none with*in a window title unlessCI_DEDICATED_MACHINE=1.Get-ProcessWindowTitlereads the titles of all visible top-level windows of a process (EnumWindows),Get-RunningLabVIEWandStop-CiLabVIEWapply it,Test-CiDedicatedMachinereads the variable.Stop-LeftoverVipmandResolve-ViServerPort -Fixuse that rule; their doc comments say that they force-close.Invoke-NativeCommand(one wrapper for console tools, replacing four copies),Invoke-Utf8CommandandInvoke-Git(UTF-8 output,Invoke-Gitthrows on a non-zero exit code),ConvertFrom-GitNameStatus(parsesgit diff -z --name-status),Get-VipmPath,Get-GhPath.ConvertTo-MarkdownText(escapes& < > |, breaks@mentions with a zero-width space) andFormat-MarkdownCodeBlock(fence longer than any backtick run in the text).Get-AnsiEncodingreplaces[Text.Encoding]::Defaultfor LabVIEW.ini and VIPM's Settings.ini (GetEncoding(0), 1252 as fallback);Add-GitHubFileTextwrites GITHUB_ENV/PATH/summary as UTF-8 without BOM.tools/ci/Resolve-CiEnvironment.ps1(modified): the clean start runs after the test and comparison LabVIEWs are chosen and passes only those two. Closing the test LabVIEW follows the same rule, and the VIObjCache is kept while that LabVIEW still runs. Uses the shared lookups and UTF-8 writes.tools/ci/New-ViDiffReport.ps1(modified):git worktree prunefirst; every git call checked; file list fromgit -c core.quotePath=false diff -z; LabVIEW's difference texts, frame captions and folder names escaped (tags dropped); CLI output in a safe fence;-LiteralPath.tools/ci/Publish-PrReport.ps1(modified): every git call checked; the report commit is built withworktree add --no-checkout,write-treeandcommit-tree, so no temporary branch exists; it is pushed by its id. When the checkout has no credentials, the push sendsGH_TOKENas theAUTHORIZATIONheader actions/checkout would store, throughGIT_CONFIG_COUNT/KEY_0/VALUE_0of that one git call (not on a command line, not in a config file). When a header is already configured, or for a local run withoutGH_TOKEN, git pushes as before. gh exit codes are checked on every call; comments are read withgh api --paginate(REST: page by page); only a marked comment of the current user (github-actions[bot]in Actions, whose token cannot read/user) is edited.tools/ci/Convert-CarayaReport.ps1(modified): parses the report by its structure (callers, then tests at one depth with their asserts one level deeper; assert level = the depth most lines share, deeper lines belong to the current test; only test lines are split into label and VI), escapes cells, safe fence for the protocol output, reads UTF-8 with-LiteralPath, acceptsnot-runfor both statuses.tools/ci/Invoke-CliTest.ps1,tools/ci/Test-InstalledPackage.ps1,tools/setup/Install-Dependencies.ps1(modified): shared wrapper and lookups,-LiteralPath.Test-InstalledPackage.ps1writes its initialize request to a temp file of its own and deletes it, and finds vipm under ProgramW6432.Install-Dependencies.ps1keeps aVIPM_DESKTOP_LIVELINESS_TIMEOUTthat is already set.tools/ci/release.workflow.yml(modified template): see the exact differences below.tools/ci/pull-request.workflow.yml(modified template): see below.tools/ci/README.md,tools/setup/README.md(modified): port resolution asResolve-ViServerPortdoes it (VIPM's port first);git restore -- testsafter a mass compile, with "checkgit statusfirst", instead ofgit checkout -- .; the release template described as it is; the isolation claim corrected (the PR's VIs and tester run on the same runner; its own scripts are used while main lacks them);CI_DEDICATED_MACHINEdocumented.No VI changed, so there is no code summary and no VI diff report.
Differences between the templates and the live workflows
tools/ci/release.workflow.ymlagainst.github/workflows/release.yml:permissions: contents: read(wascontents: write).build-and-release:environment: releaseandpermissions: contents: write.actions/checkout@v4→@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0(both jobs),actions/upload-artifact@v4→@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2,softprops/action-gh-release@v2→@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2.6.2. These are the commitsv4,v4andv2point to today (looked up withgh api repos/<owner>/<repo>/git/ref/tags/<tag>; all three are lightweight tags on commits), so the action code is unchanged.build-and-releasecheckout:fetch-depth: 0.v*tags): fetchesmain, fails unlessgit merge-base --is-ancestor HEAD origin/main.TAG_NAME: ${{ github.ref_name }}inenv:, the script reads$env:TAG_NAME(was"${{ github.ref_name }}");[Console]::OutputEncodingset to UTF-8 before the git calls; a failed tag fetch stops the step; tag text from%(contents:subject)%0a%0a%(contents:body), trimmed (was%(contents)); version pattern'^v(\d+)\.(\d+)\.(\d+)(?:\.(\d+))?$'(was unanchored"v?(\d+)...").$doubled before-replace(this was already in the template on main, not in the live file).VIP_PATHwritten with[IO.File]::AppendAllTextas UTF-8 (wasecho ... >> $env:GITHUB_ENV).tools/ci/pull-request.workflow.ymlagainst.github/workflows/pull-request.yml:env:use and UTF-8 writes.11d5960… # v4.4.0withpersist-credentials: false;upload-artifact(both) pinned toea165f8… # v4.6.2.setup,install,recheckon the three setup steps.Test-TestsWired.ps1), andsteps.wired.outcomein the last step. Already in the template on main, not in the live file.status=lines toGITHUB_OUTPUTwith[IO.File]::AppendAllText(was>>, UTF-16 in Windows PowerShell).if: ${{ !cancelled() && steps.recheck.outcome == 'success' && steps.tests.outputs.status != '2' }}(wasalways() && ...).if: ${{ !cancelled() }}; outcomes and outputs throughenv:; a skipped step is passed asnot-run; summary appended as UTF-8.if: ${{ !cancelled() && steps.setup.outcome == 'success' && steps.recheck.outcome != 'failure' }}; base branch throughenv: BASE_REF.if: ${{ !cancelled() }}. Post Report getsPR_NUMBER,HEAD_SHA,DIFF_OUTCOMEthroughenv:and builds the run URL fromGITHUB_SERVER_URL/GITHUB_REPOSITORY/GITHUB_RUN_ID; when the diff was skipped, the footer starts with "VI diff: not run".if: ${{ !cancelled() && (setup/install/recheck not success || tests/protocol failure || diff not success || wired failure) }}, all values throughenv:, an extra error line for failed setup steps and for a diff that did not run, "not run" instead of "skipped".test-build:if: github.event.pull_request.head.repo.full_name == github.repository.test-buildbuild step:PR_NUMBER,HEAD_SHAthroughenv:, run number from$env:GITHUB_RUN_NUMBER; summary andVIP_PATHwritten as UTF-8.Test-InstalledPackage.ps1), already in the template on main, not in the live file; its summary is now written as UTF-8 inside a four-backtick fence.Verified
All on this machine, Windows PowerShell 5.1.26100, without LabVIEW and without stopping any process:
.ps1/.psm1in the repository parsed with[System.Management.Automation.Language.Parser]::ParseFile: 0 errors. All changed scripts are ASCII.run:block of both templates extracted and parsed with the same parser: 14 + 9 blocks, 0 errors. No${{ }}left inside anyrun:block, everyuses:pinned to a 40-character SHA with a version comment, no tabs, final newline.Select-LabVIEWToStopwith mocked processes: clean job LabVIEW stopped, unsaved one kept (reason names the window), LabVIEW outside the job kept also withCI_DEDICATED_MACHINE=1, path compare ignores case,CI_DEDICATED_MACHINEonly for1(9 + 3 checks).Stop-LeftoverVipmwithGet-Process/Stop-Processreplaced inside the module (guarded so the test aborts if a mock is not active, process ids that cannot exist on Windows): stops exactly VIPM and the clean job LabVIEW, keeps the unsaved and the developer LabVIEW, notes the kept one.Resolve-ViServerPort -Fixwith mocks: unsaved LabVIEW not stopped, LabVIEW.ini not written, running port used; withCI_DEDICATED_MACHINE=1and for a clean LabVIEW stopped and the port written; without-Fixunchanged (10 checks).Get-ProcessWindowTitle: the EnumWindows type compiles in PowerShell 5.1 and reads a process's windows (read only).Invoke-NativeCommand: stdout and stderr as text, exit code, no stop withErrorActionPreference = Stop, missing program gives -1 and a "not recognized" line.Get-AnsiEncodinggives the same code page (1252) asEncoding.Defaultdid; LabVIEW.ini round trip keeps a non-ASCII line;Add-GitHubFileTextwrites UTF-8 without BOM.ConvertTo-MarkdownText,Format-MarkdownCodeBlock,ConvertFrom-GitNameStatus(also on a real scratch repository with a rename and a file named withü,[1],'and@),Invoke-Gitthrows on a failing call.Publish-PrReport.ps1functions taken from its syntax tree, GET requests only, against PR Show every case frame of changed VIs in the detailed diff report #29 with pages of 2: gh--paginateand REST paging return the same comments; author lookup through gh and REST, fallback togithub-actions[bot]only inside Actions; marker plus author filter; push header passed throughGIT_CONFIG_*and restored, not doubled when the checkout already has one, not used withoutGH_TOKEN(git push replaced by a recorder).Publish-PrReport.ps1run end to end offline: local bare repository as origin,ghreplaced by a stub that only records its arguments. Pushed a commit without parent containingREADME.md,images/...,report.mdtopr-assets/pr-7; no local branch or worktree left; checkout clean; gh called foruser,--paginateand one POST.Convert-CarayaReport.ps1on the most recent real report of this machine (64 tests): same tests and results as the version on main; differences are the full assert labels (main cut them at " - ") and the escaping. On crafted reports: one deeper assert line no longer hides tests,| < > @escaped in labels and checks,not-runfor both statuses, fence longer than backticks in the protocol output.%(contents)included it),üread correctly with the UTF-8 console; the anchored pattern matchesv1.2.3andv1.2.3.4and nottest-1.2.3(the old one did).Not verified
Resolve-CiEnvironment.ps1,New-ViDiffReport.ps1,Invoke-CliTest.ps1,Test-InstalledPackage.ps1,Publish-PrReport.ps1against GitHub). The push withGH_TOKENas header has not been tried against GitHub.*in every window title, and that the check sees a real LabVIEW's windows, was not observed.New-ViDiffReport.ps1escaping was not run against a real LabVIEW comparison report.GetEncoding(0)there is not tested.releaseenvironment, the tag guard and!cancelled()behaviour on GitHub.Known rough edges
*in a title for another reason keeps that LabVIEW running (the safe side).Test-InstalledPackage.ps1still closes the test LabVIEW at its end (LabVIEW CLICloseLabVIEW, thenStop-Process) without the unsaved check; it is documented as "do not point it at a LabVIEW someone works in" and was not in scope.%(contents:subject)folds the first paragraph of a tag message into one line. A tag message whose first lines are not followed by a blank line gets them joined.v*tags only;test-*tags build only a test package. Because the environment is set on the whole job,test-*builds also wait for approval.testjob and its checkouts still persist credentials (not part of the findings).git worktree prunealso runs when the scripts are used locally; it only removes worktree entries whose folders no longer exist.?for missing subVIs), but not the tests and protocol tests.Maintainer steps
not-run, the push withGH_TOKEN). The current live workflows keep working with the new scripts.release(Settings > Environments) with yourself as required reviewer, and optionally a deployment rule for tagsv*. Do this before step 3: a workflow that names a missing environment creates it without protection.v*(Settings > Rules > Rulesets, target tags,v*): restrict creation, update and deletion, with only yourself in the bypass list.CI_DEDICATED_MACHINE=1in the runner's.envfile and restart the runner. Leave it unset on the development machine.🤖 Generated with Claude Code