Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package forgejo-runner for openSUSE:Factory 
checked in at 2026-03-17 19:05:37
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/forgejo-runner (Old)
 and      /work/SRC/openSUSE:Factory/.forgejo-runner.new.8177 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "forgejo-runner"

Tue Mar 17 19:05:37 2026 rev:41 rq:1339565 version:12.7.2

Changes:
--------
--- /work/SRC/openSUSE:Factory/forgejo-runner/forgejo-runner.changes    
2026-02-23 17:04:22.966832352 +0100
+++ /work/SRC/openSUSE:Factory/.forgejo-runner.new.8177/forgejo-runner.changes  
2026-03-17 19:07:32.472239766 +0100
@@ -1,0 +2,10 @@
+Tue Mar 17 10:44:55 UTC 2026 - Richard Rahl <[email protected]>
+
+- Update to version 12.7.2:
+  * fix: only ping Forgejo during offline registration when --connect is 
enabled
+  * fix: URL comparison when determining token auth support
+- Update to version 12.7.1:
+  * fix: implement idempotent FetchTask API calls to reduce risk of lost tasks
+  * fix: accept boolean workflow_call inputs that are booleans
+
+-------------------------------------------------------------------

Old:
----
  forgejo-runner-12.7.0.obscpio

New:
----
  forgejo-runner-12.7.2.obscpio

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ forgejo-runner.spec ++++++
--- /var/tmp/diff_new_pack.QJKNVP/_old  2026-03-17 19:07:33.244271760 +0100
+++ /var/tmp/diff_new_pack.QJKNVP/_new  2026-03-17 19:07:33.248271925 +0100
@@ -18,7 +18,7 @@
 
 %define services %{name}.service
 Name:           forgejo-runner
-Version:        12.7.0
+Version:        12.7.2
 Release:        0
 Summary:        Daemon that connects to a Forgejo instance and runs CI jobs
 License:        GPL-3.0-or-later

++++++ _service ++++++
--- /var/tmp/diff_new_pack.QJKNVP/_old  2026-03-17 19:07:33.284273417 +0100
+++ /var/tmp/diff_new_pack.QJKNVP/_new  2026-03-17 19:07:33.288273584 +0100
@@ -2,7 +2,7 @@
   <service name="obs_scm" mode="manual">
     <param name="url">https://code.forgejo.org/forgejo/runner</param>
     <param name="scm">git</param>
-    <param name="revision">refs/tags/v12.7.0</param>
+    <param name="revision">refs/tags/v12.7.2</param>
     <param name="versionformat">@PARENT_TAG@</param>
     <param name="changesgenerate">disable</param>
     <param name="versionrewrite-pattern">v(.*)</param>

++++++ forgejo-runner-12.7.0.obscpio -> forgejo-runner-12.7.2.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/forgejo-runner-12.7.0/.forgejo/workflows/build-release-integration.yml 
new/forgejo-runner-12.7.2/.forgejo/workflows/build-release-integration.yml
--- old/forgejo-runner-12.7.0/.forgejo/workflows/build-release-integration.yml  
2026-02-19 02:28:46.000000000 +0100
+++ new/forgejo-runner-12.7.2/.forgejo/workflows/build-release-integration.yml  
2026-03-09 17:04:00.000000000 +0100
@@ -29,7 +29,7 @@
       - uses:  https://data.forgejo.org/actions/checkout@v4
 
       - id: forgejo
-        uses: https://data.forgejo.org/actions/[email protected]
+        uses: https://data.forgejo.org/actions/[email protected]
         with:
           user: root
           password: admin1234
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/forgejo-runner-12.7.0/.forgejo/workflows/docker-build-push-action-in-lxc.yml
 
new/forgejo-runner-12.7.2/.forgejo/workflows/docker-build-push-action-in-lxc.yml
--- 
old/forgejo-runner-12.7.0/.forgejo/workflows/docker-build-push-action-in-lxc.yml
    2026-02-19 02:28:46.000000000 +0100
+++ 
new/forgejo-runner-12.7.2/.forgejo/workflows/docker-build-push-action-in-lxc.yml
    2026-03-09 17:04:00.000000000 +0100
@@ -34,7 +34,7 @@
 
       - name: install Forgejo so it can be used as a container registry
         id: registry
-        uses: https://data.forgejo.org/actions/[email protected]
+        uses: https://data.forgejo.org/actions/[email protected]
         with:
           user: ${{ env.FORGEJO_USER }}
           password: ${{ env.FORGEJO_PASSWORD }}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/forgejo-runner-12.7.0/.forgejo/workflows/example-lxc-systemd.yml 
new/forgejo-runner-12.7.2/.forgejo/workflows/example-lxc-systemd.yml
--- old/forgejo-runner-12.7.0/.forgejo/workflows/example-lxc-systemd.yml        
2026-02-19 02:28:46.000000000 +0100
+++ new/forgejo-runner-12.7.2/.forgejo/workflows/example-lxc-systemd.yml        
2026-03-09 17:04:00.000000000 +0100
@@ -54,7 +54,7 @@
            done
 
        - id: forgejo
-         uses: https://data.forgejo.org/actions/[email protected]
+         uses: https://data.forgejo.org/actions/[email protected]
          with:
            user: root
            password: admin1234
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/forgejo-runner-12.7.0/.forgejo/workflows/example-on-demand.yml 
new/forgejo-runner-12.7.2/.forgejo/workflows/example-on-demand.yml
--- old/forgejo-runner-12.7.0/.forgejo/workflows/example-on-demand.yml  
2026-02-19 02:28:46.000000000 +0100
+++ new/forgejo-runner-12.7.2/.forgejo/workflows/example-on-demand.yml  
2026-03-09 17:04:00.000000000 +0100
@@ -29,7 +29,7 @@
 
       - uses: https://data.forgejo.org/actions/checkout@v5
 
-      - uses: https://data.forgejo.org/actions/[email protected]
+      - uses: https://data.forgejo.org/actions/[email protected]
         with:
           user: "${{ env.username }}"
           password: "${{ env.password }}"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/forgejo-runner-12.7.0/.forgejo/workflows/release-notes-assistant.yml 
new/forgejo-runner-12.7.2/.forgejo/workflows/release-notes-assistant.yml
--- old/forgejo-runner-12.7.0/.forgejo/workflows/release-notes-assistant.yml    
2026-02-19 02:28:46.000000000 +0100
+++ new/forgejo-runner-12.7.2/.forgejo/workflows/release-notes-assistant.yml    
2026-03-09 17:04:00.000000000 +0100
@@ -12,7 +12,7 @@
       - labeled
 
 env:
-  RNA_VERSION: v1.5.2 # renovate: datasource=forgejo-releases 
depName=forgejo/release-notes-assistant
+  RNA_VERSION: v1.6.1 # renovate: datasource=forgejo-releases 
depName=forgejo/release-notes-assistant
 
 jobs:
   release-notes:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/forgejo-runner-12.7.0/Makefile 
new/forgejo-runner-12.7.2/Makefile
--- old/forgejo-runner-12.7.0/Makefile  2026-02-19 02:28:46.000000000 +0100
+++ new/forgejo-runner-12.7.2/Makefile  2026-03-09 17:04:00.000000000 +0100
@@ -13,8 +13,8 @@
 GO_FMT_FILES := $(shell find . -type f -name "*.go" ! -name "generated.*")
 GOFILES := $(shell find . -type f -name "*.go" -o -name "go.mod" ! -name 
"generated.*")
 
-MOCKERY_PACKAGE ?= github.com/vektra/mockery/[email protected] # renovate: 
datasource=go
-GOLANGCI_LINT_PACKAGE ?= 
github.com/golangci/golangci-lint/v2/cmd/[email protected] # renovate: 
datasource=go
+MOCKERY_PACKAGE ?= github.com/vektra/mockery/[email protected] # renovate: 
datasource=go
+GOLANGCI_LINT_PACKAGE ?= 
github.com/golangci/golangci-lint/v2/cmd/[email protected] # renovate: 
datasource=go
 
 DOCKER_IMAGE ?= gitea/act_runner
 DOCKER_TAG ?= nightly
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/forgejo-runner-12.7.0/act/runner/expression.go 
new/forgejo-runner-12.7.2/act/runner/expression.go
--- old/forgejo-runner-12.7.0/act/runner/expression.go  2026-02-19 
02:28:46.000000000 +0100
+++ new/forgejo-runner-12.7.2/act/runner/expression.go  2026-03-09 
17:04:00.000000000 +0100
@@ -458,7 +458,7 @@
                                        _ = v.Default.Decode(&value)
                                }
                                if v.Type == "boolean" {
-                                       inputs[k] = value == "true"
+                                       inputs[k] = value == true || value == 
"true"
                                } else {
                                        inputs[k] = value
                                }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/forgejo-runner-12.7.0/act/runner/runner_test.go 
new/forgejo-runner-12.7.2/act/runner/runner_test.go
--- old/forgejo-runner-12.7.0/act/runner/runner_test.go 2026-02-19 
02:28:46.000000000 +0100
+++ new/forgejo-runner-12.7.2/act/runner/runner_test.go 2026-03-09 
17:04:00.000000000 +0100
@@ -270,7 +270,7 @@
                {workdir, "uses-composite-check-for-input-shadowing", "push", 
"", platforms, secrets},
                {workdir, "uses-nested-composite", "push", "", platforms, 
secrets},
                {workdir, "uses-composite-check-for-input-in-if-uses", "push", 
"", platforms, secrets},
-               //              {workdir, 
"remote-action-composite-js-pre-with-defaults", "push", "", platforms, secrets},
+               {workdir, "remote-action-composite-js-pre-with-defaults", 
"push", "", platforms, secrets},
                {workdir, "remote-action-composite-action-ref", "push", "", 
platforms, secrets},
                {workdir, "uses-workflow-local", "push", "", platforms, 
map[string]string{"secret": "keep_it_private"}},
                {workdir, "uses-workflow-remote-absolute", "push", "", 
platforms, map[string]string{"secret": "keep_it_private"}},
@@ -294,7 +294,7 @@
                {workdir, "checkout", "push", "", platforms, secrets},
                {workdir, "remote-action-docker", "push", "", platforms, 
secrets},
                {workdir, "remote-action-js", "push", "", platforms, secrets},
-               // {workdir, "remote-action-js-node-user", "push", "", 
platforms, secrets}, // Test if this works with non root container
+               {workdir, "remote-action-js-node-user", "push", "", platforms, 
secrets},
                {workdir, "matrix", "push", "", platforms, secrets},
                {workdir, "matrix-include-exclude", "push", "", platforms, 
secrets},
                {workdir, "matrix-exitcode", "push", "Job 'test' failed", 
platforms, secrets},
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/forgejo-runner-12.7.0/act/runner/step_action_remote.go 
new/forgejo-runner-12.7.2/act/runner/step_action_remote.go
--- old/forgejo-runner-12.7.0/act/runner/step_action_remote.go  2026-02-19 
02:28:46.000000000 +0100
+++ new/forgejo-runner-12.7.2/act/runner/step_action_remote.go  2026-03-09 
17:04:00.000000000 +0100
@@ -32,7 +32,7 @@
 
 var stepActionRemoteGitClone = git.Clone
 
-var protoRegex = regexp.MustCompile("^https?://")
+var schemePattern = regexp.MustCompile("^https?://")
 
 func (sar *stepActionRemote) prepareActionExecutor() common.Executor {
        return func(ctx context.Context) error {
@@ -71,11 +71,16 @@
                        if err != nil {
                                common.Logger(ctx).Warnf("failed to determine 
token auth support from server version '%s': %w", 
sar.RunContext.Config.ServerVersion, err)
                        }
+                       common.Logger(ctx).Debugf("Authentication with token 
supported: %v", tokenSupported)
 
                        // If we're pulling the action from the instance 
itself, set the auth token
-                       if tokenSupported && (sar.remoteAction.URL == "" && 
sar.RunContext.Config.DefaultActionInstance == 
sar.RunContext.Config.GitHubInstance) || 
(protoRegex.ReplaceAllString(sar.remoteAction.URL, "") == 
sar.RunContext.Config.GitHubInstance) {
+                       if tokenSupported && 
actionHostedOnSameInstanceAsWorkflow(sar.remoteAction.URL, 
sar.RunContext.Config.GitHubInstance, 
sar.RunContext.Config.DefaultActionInstance) {
                                token = github.Token
+                       } else {
+                               common.Logger(ctx).Debugf("Authentication with 
token disabled for action URL %q, origin URL %q, and default URL %q",
+                                       sar.remoteAction.URL, 
sar.RunContext.Config.GitHubInstance, 
sar.RunContext.Config.DefaultActionInstance)
                        }
+
                        wt, err := stepActionRemoteGitClone(ctx, git.CloneInput{
                                CacheDir:    sar.RunContext.ActionCacheDir(),
                                URL:         
sar.remoteAction.CloneURL(sar.RunContext.Config.DefaultActionInstance),
@@ -332,3 +337,35 @@
 
        return c.Check(v), nil
 }
+
+// actionHostedOnSameInstanceAsWorkflow tests whether an action is hosted on 
the same instance as the workflow that is
+// being run. Returns `true` if both URLs match (including scheme, ports, and 
paths), `false` otherwise.
+func actionHostedOnSameInstanceAsWorkflow(actionURL, originInstance, 
defaultActionsInstance string) bool {
+       // Remove trailing slashes if present. It preserves the meaning of the 
URLs while removing a source of configuration
+       // problems.
+       actionURL = strings.TrimSuffix(actionURL, "/")
+       originInstance = strings.TrimSuffix(originInstance, "/")
+       defaultActionsInstance = strings.TrimSuffix(defaultActionsInstance, "/")
+
+       isHostname := func(s string) bool {
+               return !strings.HasPrefix(s, "http://";) && 
!strings.HasPrefix(s, "https://";)
+       }
+
+       // Handle an action reference with an empty URL like `uses: 
actions/checkout`.
+       if actionURL == "" {
+               if isHostname(originInstance) || 
isHostname(defaultActionsInstance) {
+                       originInstance = 
schemePattern.ReplaceAllString(originInstance, "")
+                       defaultActionsInstance = 
schemePattern.ReplaceAllString(defaultActionsInstance, "")
+               }
+               return originInstance == defaultActionsInstance
+       }
+
+       // Test if an action loaded from an arbitrary URL (like `uses: 
https://forge.example.com/actions/checkout`) comes
+       // from the same instance as the workflow that is being run. Because 
Forgejo can be hosted on paths or use
+       // arbitrary ports, looking at the hostname is not sufficient. Both 
URLs have to be equal.
+       if isHostname(originInstance) || isHostname(actionURL) {
+               originInstance = schemePattern.ReplaceAllString(originInstance, 
"")
+               actionURL = schemePattern.ReplaceAllString(actionURL, "")
+       }
+       return originInstance == actionURL
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/forgejo-runner-12.7.0/act/runner/step_action_remote_test.go 
new/forgejo-runner-12.7.2/act/runner/step_action_remote_test.go
--- old/forgejo-runner-12.7.0/act/runner/step_action_remote_test.go     
2026-02-19 02:28:46.000000000 +0100
+++ new/forgejo-runner-12.7.2/act/runner/step_action_remote_test.go     
2026-03-09 17:04:00.000000000 +0100
@@ -648,7 +648,7 @@
                expectedToken         string
        }{
                {
-                       name:                  "Token set when instances match 
and default actions URL is used",
+                       name:                  "Token set when instance 
hostnames match and default actions URL is used",
                        defaultActionInstance: "forgejo.my-instance.com",
                        githubInstance:        "forgejo.my-instance.com",
                        serverVersion:         "14.0.0",
@@ -657,6 +657,51 @@
                        expectedToken:         "test-token",
                },
                {
+                       name:                  "Token set when instance 
hostnames match and default actions URL is a hostname",
+                       defaultActionInstance: "forgejo.my-instance.com",
+                       githubInstance:        
"https://forgejo.my-instance.com";,
+                       serverVersion:         "14.0.0",
+                       remoteURL:             "",
+                       token:                 "test-token",
+                       expectedToken:         "test-token",
+               },
+               {
+                       name:                  "Token empty when instance 
hostnames match but port not",
+                       defaultActionInstance: "forgejo.my-instance.com:8080",
+                       githubInstance:        "forgejo.my-instance.com",
+                       serverVersion:         "14.0.0",
+                       remoteURL:             "",
+                       token:                 "test-token",
+                       expectedToken:         "",
+               },
+               {
+                       name:                  "Token set when instance URLs 
match and default actions URL is used",
+                       defaultActionInstance: 
"https://forgejo.my-instance.com/";,
+                       githubInstance:        
"https://forgejo.my-instance.com";,
+                       serverVersion:         "14.0.0",
+                       remoteURL:             "",
+                       token:                 "test-token",
+                       expectedToken:         "test-token",
+               },
+               {
+                       name:                  "Token empty when scheme of 
instance URL and default actions URL do not match",
+                       defaultActionInstance: 
"https://forgejo.my-instance.com/";,
+                       githubInstance:        "http://forgejo.my-instance.com";,
+                       serverVersion:         "14.0.0",
+                       remoteURL:             "",
+                       token:                 "test-token",
+                       expectedToken:         "",
+               },
+               {
+                       name:                  "Token empty when port of 
instance URL and default actions URL do not match",
+                       defaultActionInstance: 
"https://forgejo.my-instance.com:8080/";,
+                       githubInstance:        
"https://forgejo.my-instance.com";,
+                       serverVersion:         "14.0.0",
+                       remoteURL:             "",
+                       token:                 "test-token",
+                       expectedToken:         "",
+               },
+               {
                        name:                  "Token empty when instances 
differ and default actions URL is used",
                        defaultActionInstance: "forgejo.my-instance.com",
                        githubInstance:        "forgejo.other-instance.com",
@@ -675,7 +720,7 @@
                        expectedToken:         "",
                },
                {
-                       name:                  "Token set when explicit URL is 
provided and it matches the instance URL",
+                       name:                  "Token set when explicit URL is 
provided and it matches the instance hostname",
                        defaultActionInstance: "forgejo.my-instance.com",
                        githubInstance:        "forgejo.my-instance.com",
                        serverVersion:         "14.0.0",
@@ -683,6 +728,15 @@
                        token:                 "test-token",
                        expectedToken:         "test-token",
                },
+               {
+                       name:                  "Token set when explicit URL is 
provided and it matches the instance URL",
+                       defaultActionInstance: "https://example.com/";,
+                       githubInstance:        
"https://forgejo.my-instance.com:8080/";,
+                       serverVersion:         "14.0.0",
+                       remoteURL:             
"https://forgejo.my-instance.com:8080";,
+                       token:                 "test-token",
+                       expectedToken:         "test-token",
+               },
                {
                        name:                  "Token empty when server version 
< 13.0.0",
                        defaultActionInstance: "forgejo.my-instance.com",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/forgejo-runner-12.7.0/act/runner/testdata/remote-action-js-node-user/push.yml
 
new/forgejo-runner-12.7.2/act/runner/testdata/remote-action-js-node-user/push.yml
--- 
old/forgejo-runner-12.7.0/act/runner/testdata/remote-action-js-node-user/push.yml
   2026-02-19 02:28:46.000000000 +0100
+++ 
new/forgejo-runner-12.7.2/act/runner/testdata/remote-action-js-node-user/push.yml
   2026-03-09 17:04:00.000000000 +0100
@@ -27,4 +27,4 @@
       with:
         who-to-greet: 'Mona the Octocat'
 
-    - uses: cloudposse/actions/github/[email protected]
+    - uses: 
https://github.com/cloudposse/actions/github/[email protected]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/forgejo-runner-12.7.0/act/runner/testdata/workflow_call_inputs/workflow_call_inputs.yml
 
new/forgejo-runner-12.7.2/act/runner/testdata/workflow_call_inputs/workflow_call_inputs.yml
--- 
old/forgejo-runner-12.7.0/act/runner/testdata/workflow_call_inputs/workflow_call_inputs.yml
 2026-02-19 02:28:46.000000000 +0100
+++ 
new/forgejo-runner-12.7.2/act/runner/testdata/workflow_call_inputs/workflow_call_inputs.yml
 2026-03-09 17:04:00.000000000 +0100
@@ -14,6 +14,10 @@
         description: an input of type boolean
         required: false
         type: boolean
+      boolean_with_default:
+        description: an input of type boolean with a default value
+        default: true
+        type: boolean
 
 jobs:
   test:
@@ -21,11 +25,11 @@
     steps:
       - name: test required input
         run: |
-          echo input.required=${{ inputs.required }}
+          echo inputs.required=${{ inputs.required }}
           [[ "${{ inputs.required }}" = "required input" ]] || exit 1
       - name: test input with default
         run: |
-          echo input.with_default=${{ inputs.with_default }}
+          echo inputs.with_default=${{ inputs.with_default }}
           [[ "${{ inputs.with_default }}" = "default" ]] || exit 1
       - id: boolean-test
         name: run on boolean input
@@ -34,3 +38,7 @@
       - name: has boolean test?
         run: |
           [[ "${{ steps.boolean-test.outputs.value }}" = "executed" ]] || exit 
1
+      - name: test boolean with default input
+        run: |
+          echo "inputs.boolean_with_default=${{ inputs.boolean_with_default }}"
+          [[ "${{ inputs.boolean_with_default }}" == "true" ]] || exit 1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/forgejo-runner-12.7.0/act/workflowpattern/workflow_pattern.go 
new/forgejo-runner-12.7.2/act/workflowpattern/workflow_pattern.go
--- old/forgejo-runner-12.7.0/act/workflowpattern/workflow_pattern.go   
2026-02-19 02:28:46.000000000 +0100
+++ new/forgejo-runner-12.7.2/act/workflowpattern/workflow_pattern.go   
2026-03-09 17:04:00.000000000 +0100
@@ -127,7 +127,7 @@
                        if errorMessage.Len() > 0 {
                                errorMessage.WriteString(", ")
                        }
-                       errorMessage.WriteString(fmt.Sprintf("Position: %d 
Error: %s", position, err))
+                       fmt.Fprintf(&errorMessage, "Position: %d Error: %s", 
position, err)
                }
                return "", fmt.Errorf("invalid Pattern '%s': %s", pattern, 
errorMessage.String())
        }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/forgejo-runner-12.7.0/examples/docker-compose/compose-forgejo-and-runner.yml
 
new/forgejo-runner-12.7.2/examples/docker-compose/compose-forgejo-and-runner.yml
--- 
old/forgejo-runner-12.7.0/examples/docker-compose/compose-forgejo-and-runner.yml
    2026-02-19 02:28:46.000000000 +0100
+++ 
new/forgejo-runner-12.7.2/examples/docker-compose/compose-forgejo-and-runner.yml
    2026-03-09 17:04:00.000000000 +0100
@@ -51,7 +51,7 @@
       - 8080:3000
 
   runner-register:
-    image: data.forgejo.org/forgejo/runner:12.6.4
+    image: data.forgejo.org/forgejo/runner:12.7.1
     links:
       - docker-in-docker
       - forgejo
@@ -66,7 +66,7 @@
         forgejo-runner create-runner-file --connect --instance 
http://forgejo:3000 --name runner --secret {SHARED_SECRET} && break ;
         sleep 1 ;
       done ;
-      sed -i -e "s|\"labels\": null|\"labels\": 
[\"docker-cli:docker://code.forgejo.org/oci/docker:cli\",\"node-bookworm:docker://code.forgejo.org/oci/node:20-bookworm\"]|"
 .runner ;
+      sed -i -e "s|\"labels\": \[\]|\"labels\": 
[\"docker-cli:docker://code.forgejo.org/oci/docker:cli\",\"node-bookworm:docker://code.forgejo.org/oci/node:20-bookworm\"]|"
 .runner ;
       forgejo-runner generate-config > config.yml ;
       sed -i -e "s|  level: info|  level: debug|" config.yml ;
       sed -i -e "s|network: .*|network: host|" config.yml ;
@@ -77,7 +77,7 @@
       '
 
   runner-daemon:
-    image: data.forgejo.org/forgejo/runner:12.6.4
+    image: data.forgejo.org/forgejo/runner:12.7.1
     links:
       - docker-in-docker
       - forgejo
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/forgejo-runner-12.7.0/examples/lxc-systemd/forgejo-runner-service.sh 
new/forgejo-runner-12.7.2/examples/lxc-systemd/forgejo-runner-service.sh
--- old/forgejo-runner-12.7.0/examples/lxc-systemd/forgejo-runner-service.sh    
2026-02-19 02:28:46.000000000 +0100
+++ new/forgejo-runner-12.7.2/examples/lxc-systemd/forgejo-runner-service.sh    
2026-03-09 17:04:00.000000000 +0100
@@ -22,13 +22,13 @@
 : ${INPUTS_LIFETIME:=7d}
 DEFAULT_LXC_HELPERS_VERSION=1.1.3 # renovate: datasource=forgejo-tags 
depName=forgejo/lxc-helpers
 : ${INPUTS_LXC_HELPERS_VERSION:=$DEFAULT_LXC_HELPERS_VERSION}
-DEFAULT_RUNNER_VERSION=12.6.4 # renovate: datasource=forgejo-releases 
depName=forgejo/runner
+DEFAULT_RUNNER_VERSION=12.7.1 # renovate: datasource=forgejo-releases 
depName=forgejo/runner
 : ${INPUTS_RUNNER_VERSION:=$DEFAULT_RUNNER_VERSION}
 
 : ${KILL_AFTER:=21600} # 6h == 21600
 NODEJS_VERSION=24 # renovate: datasource=node-version 
depName=forgejo-runner-service-node
 DEBIAN_RELEASE=trixie # renovate: datasource=docker 
depName=forgejo-runner-service-debian packageName=code.forgejo.org/oci/debian 
versioning=debian
-YQ_VERSION=v4.52.2 # renovate: datasource=github-releases 
depName=forgejo-runner-service-yq packageName=mikefarah/yq
+YQ_VERSION=v4.52.4 # renovate: datasource=github-releases 
depName=forgejo-runner-service-yq packageName=mikefarah/yq
 SELF=${BASH_SOURCE[0]}
 SELF_FILENAME=$(basename "$SELF")
 SELF_INSTALLED=/usr/local/bin/$SELF_FILENAME
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/forgejo-runner-12.7.0/go.mod 
new/forgejo-runner-12.7.2/go.mod
--- old/forgejo-runner-12.7.0/go.mod    2026-02-19 02:28:46.000000000 +0100
+++ new/forgejo-runner-12.7.2/go.mod    2026-03-09 17:04:00.000000000 +0100
@@ -2,7 +2,7 @@
 
 go 1.25.0
 
-toolchain go1.25.7
+toolchain go1.25.8
 
 require (
        code.forgejo.org/forgejo/actions-proto v0.6.0
@@ -18,8 +18,8 @@
        github.com/docker/cli v28.5.2+incompatible
        github.com/docker/docker v28.5.2+incompatible
        github.com/docker/go-connections v0.6.0
-       github.com/go-git/go-billy/v5 v5.6.2
-       github.com/go-git/go-git/v5 v5.16.5
+       github.com/go-git/go-billy/v5 v5.8.0
+       github.com/go-git/go-git/v5 v5.17.0
        github.com/gobwas/glob v0.2.3
        github.com/google/go-cmp v0.7.0
        github.com/google/uuid v1.6.0
@@ -39,9 +39,9 @@
        github.com/timshannon/bolthold v0.0.0-20240314194003-30aac6950928
        go.etcd.io/bbolt v1.4.3
        go.yaml.in/yaml/v3 v3.0.4
-       golang.org/x/sys v0.41.0
+       golang.org/x/sys v0.42.0
        golang.org/x/term v0.40.0
-       golang.org/x/time v0.14.0
+       golang.org/x/time v0.15.0
        google.golang.org/protobuf v1.36.11
        gotest.tools/v3 v3.5.2
 )
@@ -52,7 +52,8 @@
        github.com/Microsoft/go-winio v0.6.2 // indirect
        github.com/ProtonMail/go-crypto v1.1.6 // indirect
        github.com/bmatcuk/doublestar/v4 v4.9.1 // indirect
-       github.com/cloudflare/circl v1.6.1 // indirect
+       github.com/cespare/xxhash/v2 v2.3.0 // indirect
+       github.com/cloudflare/circl v1.6.3 // indirect
        github.com/containerd/errdefs/pkg v0.3.0 // indirect
        github.com/containerd/log v0.1.0 // indirect
        github.com/cyphar/filepath-securejoin v0.6.0 // indirect
@@ -63,7 +64,7 @@
        github.com/fatih/color v1.18.0 // indirect
        github.com/felixge/httpsnoop v1.0.4 // indirect
        github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
-       github.com/go-logr/logr v1.4.2 // indirect
+       github.com/go-logr/logr v1.4.3 // indirect
        github.com/go-logr/stdr v1.2.2 // indirect
        github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
        github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // 
indirect
@@ -95,13 +96,13 @@
        github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // 
indirect
        github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 
// indirect
        github.com/xeipuuv/gojsonschema v1.2.0 // indirect
-       go.opentelemetry.io/auto/sdk v1.1.0 // indirect
+       go.opentelemetry.io/auto/sdk v1.2.1 // indirect
        go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 
// indirect
-       go.opentelemetry.io/otel v1.36.0 // indirect
+       go.opentelemetry.io/otel v1.40.0 // indirect
        go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 
// indirect
-       go.opentelemetry.io/otel/metric v1.36.0 // indirect
-       go.opentelemetry.io/otel/sdk v1.21.0 // indirect
-       go.opentelemetry.io/otel/trace v1.36.0 // indirect
+       go.opentelemetry.io/otel/metric v1.40.0 // indirect
+       go.opentelemetry.io/otel/sdk v1.40.0 // indirect
+       go.opentelemetry.io/otel/trace v1.40.0 // indirect
        go.yaml.in/yaml/v4 v4.0.0-rc.3 // indirect
        golang.org/x/crypto v0.45.0 // indirect
        golang.org/x/net v0.47.0 // indirect
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/forgejo-runner-12.7.0/go.sum 
new/forgejo-runner-12.7.2/go.sum
--- old/forgejo-runner-12.7.0/go.sum    2026-02-19 02:28:46.000000000 +0100
+++ new/forgejo-runner-12.7.2/go.sum    2026-03-09 17:04:00.000000000 +0100
@@ -27,8 +27,10 @@
 github.com/bmatcuk/doublestar/v4 v4.9.1/go.mod 
h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
 github.com/cenkalti/backoff/v4 v4.2.1 
h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
 github.com/cenkalti/backoff/v4 v4.2.1/go.mod 
h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
-github.com/cloudflare/circl v1.6.1 
h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
-github.com/cloudflare/circl v1.6.1/go.mod 
h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
+github.com/cespare/xxhash/v2 v2.3.0 
h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
+github.com/cespare/xxhash/v2 v2.3.0/go.mod 
h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cloudflare/circl v1.6.3 
h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
+github.com/cloudflare/circl v1.6.3/go.mod 
h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
 github.com/containerd/errdefs v1.0.0 
h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
 github.com/containerd/errdefs v1.0.0/go.mod 
h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
 github.com/containerd/errdefs/pkg v0.3.0 
h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
@@ -72,15 +74,15 @@
 github.com/gliderlabs/ssh v0.3.8/go.mod 
h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
 github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 
h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
 github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod 
h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
-github.com/go-git/go-billy/v5 v5.6.2 
h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=
-github.com/go-git/go-billy/v5 v5.6.2/go.mod 
h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
+github.com/go-git/go-billy/v5 v5.8.0 
h1:I8hjc3LbBlXTtVuFNJuwYuMiHvQJDq1AT6u4DwDzZG0=
+github.com/go-git/go-billy/v5 v5.8.0/go.mod 
h1:RpvI/rw4Vr5QA+Z60c6d6LXH0rYJo0uD5SqfmrrheCY=
 github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 
h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
 github.com/go-git/go-git-fixtures/v4 
v4.3.2-0.20231010084843-55a94097c399/go.mod 
h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
-github.com/go-git/go-git/v5 v5.16.5 
h1:mdkuqblwr57kVfXri5TTH+nMFLNUxIj9Z7F5ykFbw5s=
-github.com/go-git/go-git/v5 v5.16.5/go.mod 
h1:QOMLpNf1qxuSY4StA/ArOdfFR2TrKEjJiye2kel2m+M=
+github.com/go-git/go-git/v5 v5.17.0 
h1:AbyI4xf+7DsjINHMu35quAh4wJygKBKBuXVjV/pxesM=
+github.com/go-git/go-git/v5 v5.17.0/go.mod 
h1:f82C4YiLx+Lhi8eHxltLeGC5uBTXSFa6PC5WW9o4SjI=
 github.com/go-logr/logr v1.2.2/go.mod 
h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
-github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
-github.com/go-logr/logr v1.4.2/go.mod 
h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
+github.com/go-logr/logr v1.4.3/go.mod 
h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
 github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
 github.com/go-logr/stdr v1.2.2/go.mod 
h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
 github.com/go-viper/mapstructure/v2 v2.4.0 
h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
@@ -208,22 +210,22 @@
 go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
 go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
 go.etcd.io/gofail v0.1.0/go.mod h1:VZBCXYGZhHAinaBiiqYvuDynvahNsAyLFwB3kEHKz1M=
-go.opentelemetry.io/auto/sdk v1.1.0 
h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
-go.opentelemetry.io/auto/sdk v1.1.0/go.mod 
h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
+go.opentelemetry.io/auto/sdk v1.2.1 
h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
+go.opentelemetry.io/auto/sdk v1.2.1/go.mod 
h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 
h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24=
 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod 
h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo=
-go.opentelemetry.io/otel v1.36.0 
h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
-go.opentelemetry.io/otel v1.36.0/go.mod 
h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
+go.opentelemetry.io/otel v1.40.0 
h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
+go.opentelemetry.io/otel v1.40.0/go.mod 
h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 
h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw=
 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod 
h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg=
 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 
h1:digkEZCJWobwBqMwC0cwCq8/wkkRy/OowZg5OArWZrM=
 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0/go.mod 
h1:/OpE/y70qVkndM0TrxT4KBoN3RsFZP0QaofcfYrj76I=
-go.opentelemetry.io/otel/metric v1.36.0 
h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
-go.opentelemetry.io/otel/metric v1.36.0/go.mod 
h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
-go.opentelemetry.io/otel/sdk v1.21.0 
h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8=
-go.opentelemetry.io/otel/sdk v1.21.0/go.mod 
h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
-go.opentelemetry.io/otel/trace v1.36.0 
h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
-go.opentelemetry.io/otel/trace v1.36.0/go.mod 
h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
+go.opentelemetry.io/otel/metric v1.40.0 
h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
+go.opentelemetry.io/otel/metric v1.40.0/go.mod 
h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
+go.opentelemetry.io/otel/sdk v1.40.0 
h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=
+go.opentelemetry.io/otel/sdk v1.40.0/go.mod 
h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE=
+go.opentelemetry.io/otel/trace v1.40.0 
h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
+go.opentelemetry.io/otel/trace v1.40.0/go.mod 
h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
 go.opentelemetry.io/proto/otlp v1.0.0 
h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
 go.opentelemetry.io/proto/otlp v1.0.0/go.mod 
h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
 go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
@@ -250,16 +252,16 @@
 golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
-golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
+golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod 
h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
 golang.org/x/term v0.40.0/go.mod 
h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
 golang.org/x/text v0.31.0/go.mod 
h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
-golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
-golang.org/x/time v0.14.0/go.mod 
h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
+golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
+golang.org/x/time v0.15.0/go.mod 
h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod 
h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 
h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ=
 google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f 
h1:2yNACc1O40tTnrsbk9Cv6oxiW8pxI/pXj0wRtdlYmgY=
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/forgejo-runner-12.7.0/internal/app/cmd/create-runner-file.go 
new/forgejo-runner-12.7.2/internal/app/cmd/create-runner-file.go
--- old/forgejo-runner-12.7.0/internal/app/cmd/create-runner-file.go    
2026-02-19 02:28:46.000000000 +0100
+++ new/forgejo-runner-12.7.2/internal/app/cmd/create-runner-file.go    
2026-03-09 17:04:00.000000000 +0100
@@ -127,8 +127,10 @@
                //
                // Verify the Forgejo instance is reachable
                //
-               if err := ping(cfg, reg); err != nil {
-                       return err
+               if args.Connect {
+                       if err := ping(cfg, reg); err != nil {
+                               return err
+                       }
                }
 
                //
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/forgejo-runner-12.7.0/internal/app/cmd/create-runner-file_test.go 
new/forgejo-runner-12.7.2/internal/app/cmd/create-runner-file_test.go
--- old/forgejo-runner-12.7.0/internal/app/cmd/create-runner-file_test.go       
2026-02-19 02:28:46.000000000 +0100
+++ new/forgejo-runner-12.7.2/internal/app/cmd/create-runner-file_test.go       
2026-03-09 17:04:00.000000000 +0100
@@ -13,6 +13,7 @@
        "code.forgejo.org/forgejo/runner/v12/internal/pkg/config"
        "code.forgejo.org/forgejo/runner/v12/internal/pkg/ver"
        "connectrpc.com/connect"
+       "github.com/stretchr/testify/require"
 
        "github.com/stretchr/testify/assert"
        "go.yaml.in/yaml/v3"
@@ -61,6 +62,29 @@
        assert.NoError(t, ping(cfg, reg))
 }
 
+func TestCreateRunnerFile_OfflineRunnerFileCreation(t *testing.T) {
+       configFile, configuration, err := prepareConfig(t.TempDir())
+       require.NoError(t, err)
+
+       secret := "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+       instance := "https://example.com/";
+       name := "offline-runner"
+
+       cmd := createRunnerFileCmd(t.Context(), &configFile)
+       output, _, _, err := executeCommand(t.Context(), t, cmd, "--secret", 
secret, "--instance", instance, "--name", name)
+
+       require.NoError(t, err)
+       assert.Empty(t, output)
+
+       reg, err := config.LoadRegistration(configuration.Runner.File)
+       require.NoError(t, err)
+       assert.Zero(t, reg.ID)
+       assert.Equal(t, "41414141-4141-4141-4141-414141414141", reg.UUID)
+       assert.Equal(t, secret, reg.Token)
+       assert.Equal(t, instance, reg.Address)
+       assert.Equal(t, name, reg.Name)
+}
+
 func Test_runCreateRunnerFile(t *testing.T) {
        if testing.Short() {
                t.Skip("skipping integration test")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/forgejo-runner-12.7.0/internal/app/job/job.go 
new/forgejo-runner-12.7.2/internal/app/job/job.go
--- old/forgejo-runner-12.7.0/internal/app/job/job.go   2026-02-19 
02:28:46.000000000 +0100
+++ new/forgejo-runner-12.7.2/internal/app/job/job.go   2026-03-09 
17:04:00.000000000 +0100
@@ -49,21 +49,7 @@
        if !ok {
                return fmt.Errorf("could not fetch task")
        }
-       return j.runTaskWithRecover(ctx, task)
-}
-
-func (j *Job) runTaskWithRecover(ctx context.Context, task *runnerv1.Task) 
error {
-       defer func() {
-               if r := recover(); r != nil {
-                       err := fmt.Errorf("panic: %v", r)
-                       log.WithError(err).Error("panic in runTaskWithRecover")
-               }
-       }()
-
-       if err := j.runner.Run(ctx, task); err != nil {
-               log.WithError(err).Error("failed to run task")
-               return err
-       }
+       j.runner.Run(ctx, task)
        return nil
 }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/forgejo-runner-12.7.0/internal/app/job/job_test.go 
new/forgejo-runner-12.7.2/internal/app/job/job_test.go
--- old/forgejo-runner-12.7.0/internal/app/job/job_test.go      2026-02-19 
02:28:46.000000000 +0100
+++ new/forgejo-runner-12.7.2/internal/app/job/job_test.go      2026-03-09 
17:04:00.000000000 +0100
@@ -17,6 +17,7 @@
        "code.forgejo.org/forgejo/runner/v12/internal/pkg/client"
        "code.forgejo.org/forgejo/runner/v12/internal/pkg/config"
 
+       gouuid "github.com/google/uuid"
        log "github.com/sirupsen/logrus"
        "github.com/stretchr/testify/assert"
 )
@@ -43,6 +44,10 @@
        return time.Second
 }
 
+func (o mockClient) SetRequestKey(requestKey gouuid.UUID) func() {
+       return func() {}
+}
+
 func (o *mockClient) FetchTask(ctx context.Context, _ 
*connect.Request[runnerv1.FetchTaskRequest]) 
(*connect.Response[runnerv1.FetchTaskResponse], error) {
        if o.sleep > 0 {
                select {
@@ -73,38 +78,19 @@
 }
 
 type mockRunner struct {
-       cfg    *config.Runner
-       log    chan string
-       panics bool
-       err    error
+       cfg *config.Runner
+       log chan string
 }
 
-func (o *mockRunner) Run(ctx context.Context, _ *runnerv1.Task) error {
+func (o *mockRunner) Run(ctx context.Context, _ *runnerv1.Task) {
        o.log <- "runner starts"
-       if o.panics {
-               log.Trace("panics")
-               o.log <- "runner panics"
-               o.panics = false
-               panic("whatever")
-       }
-       if o.err != nil {
-               log.Trace("error")
-               o.log <- "runner error"
-               err := o.err
-               o.err = nil
-               return err
-       }
-       for {
-               select {
-               case <-ctx.Done():
-                       log.Trace("shutdown")
-                       o.log <- "runner shutdown"
-                       return nil
-               case <-time.After(o.cfg.Timeout):
-                       log.Trace("after")
-                       o.log <- "runner timeout"
-                       return nil
-               }
+       select {
+       case <-ctx.Done():
+               log.Trace("shutdown")
+               o.log <- "runner shutdown"
+       case <-time.After(o.cfg.Timeout):
+               log.Trace("after")
+               o.log <- "runner timeout"
        }
 }
 
@@ -202,8 +188,6 @@
                name          string
                noTask        bool
                clientErr     error
-               runnerErr     error
-               runnerPanics  bool
                expectError   bool
                errorContains string
        }{
@@ -223,16 +207,6 @@
                        expectError:   true,
                        errorContains: "could not fetch task",
                },
-               {
-                       name:        "Runner error",
-                       runnerErr:   fmt.Errorf("runner failed"),
-                       expectError: true,
-               },
-               {
-                       name:         "Runner panics",
-                       runnerPanics: true,
-                       expectError:  false,
-               },
        } {
                t.Run(testCase.name, func(t *testing.T) {
                        logChan := make(chan string, 10)
@@ -250,10 +224,8 @@
                                        err:    testCase.clientErr,
                                },
                                &mockRunner{
-                                       cfg:    &configRunner,
-                                       log:    logChan,
-                                       panics: testCase.runnerPanics,
-                                       err:    testCase.runnerErr,
+                                       cfg: &configRunner,
+                                       log: logChan,
                                },
                        )
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/forgejo-runner-12.7.0/internal/app/poll/poller.go 
new/forgejo-runner-12.7.2/internal/app/poll/poller.go
--- old/forgejo-runner-12.7.0/internal/app/poll/poller.go       2026-02-19 
02:28:46.000000000 +0100
+++ new/forgejo-runner-12.7.2/internal/app/poll/poller.go       2026-03-09 
17:04:00.000000000 +0100
@@ -6,7 +6,6 @@
 import (
        "context"
        "errors"
-       "fmt"
        "sync"
        "sync/atomic"
 
@@ -18,6 +17,7 @@
        "code.forgejo.org/forgejo/runner/v12/internal/app/run"
        "code.forgejo.org/forgejo/runner/v12/internal/pkg/client"
        "code.forgejo.org/forgejo/runner/v12/internal/pkg/config"
+       gouuid "github.com/google/uuid"
 )
 
 const PollerID = "PollerID"
@@ -115,6 +115,22 @@
                return
        }
 
+       // When the FetchTask() API is invoked to create a task, unpreventable 
environmental errors may occur; for example,
+       // network disconnects and timeouts. It's possible that these errors 
occur after the server-side has assigned a task
+       // to the runner during the API call, in which case the error would 
cause that task to be lost between the two
+       // systems -- the server will think it's assigned to the runner, and 
the runner never received it.
+       //
+       // The solution implemented here is idempotency in the FetchTask() API 
call, which means that the "same" FetchTask()
+       // API call is expected to return the same values. Specifically, the 
runner creates a unique identifier `requestKey`
+       // which is transmitted to the server along with each FetchTask() 
invocation which defines the sameness of the call,
+       // and the runner retains the `requestKey` value until the API call 
receives a successful response. If the server
+       // implements idempotency, it can use this key to identify repeated 
invocations of FetchTask() and when the same
+       // request is received, the same response is provided.
+       //
+       // Runner's responsibility is to send the same request key consistently 
if any error occurred, and, change it to a
+       // new key when a successful call is received.
+       requestKey := gouuid.New()
+
        for {
                if err := limiter.Wait(p.pollingCtx); err != nil {
                        log.Infof("[poller] shutdown begin, %d tasks currently 
running", inProgressTasks.Load())
@@ -125,20 +141,24 @@
                availableCapacity := capacity - inProgressTasks.Load()
                if availableCapacity > 0 {
                        log.Tracef("[poller] fetching at most %d tasks from 
client %s", availableCapacity, client.Address())
-                       tasks, ok := p.fetchTasks(p.pollingCtx, client, 
taskVersions, availableCapacity)
+                       tasks, reuseRequestKey := p.fetchTasks(p.pollingCtx, 
client, taskVersions, availableCapacity, requestKey)
                        inProgressTasks.Add(int64(len(tasks)))
                        if len(tasks) > 0 && ephemeral {
                                p.shutdownPolling()
                        }
                        <-fetchMutex // unlock mutex by draining channel
-                       if !ok {
+
+                       if !reuseRequestKey {
+                               requestKey = gouuid.New()
+                       }
+                       if len(tasks) == 0 {
                                continue
                        }
 
                        log.Tracef("[poller] successfully fetched %d tasks from 
client %s", len(tasks), client.Address())
                        for _, task := range tasks {
                                wg.Go(func() {
-                                       p.runTaskWithRecover(p.jobsCtx, runner, 
task)
+                                       runner.Run(p.jobsCtx, task)
                                        inProgressTasks.Add(-1)
                                })
                        }
@@ -165,22 +185,15 @@
        }
 }
 
-func (p *poller) runTaskWithRecover(ctx context.Context, runner 
run.RunnerInterface, task *runnerv1.Task) {
-       defer func() {
-               if r := recover(); r != nil {
-                       err := fmt.Errorf("panic: %v", r)
-                       log.WithError(err).Error("panic in runTaskWithRecover")
-               }
-       }()
+func (p *poller) fetchTasks(ctx context.Context, client client.Client, 
tasksVersion *atomic.Int64, availableCapacity int64, requestKey gouuid.UUID) 
(taskSlice []*runnerv1.Task, reuseRequestKey bool) {
+       taskSlice = nil
+       reuseRequestKey = true // Default to reusing requestKey until we get a 
successful response
 
-       if err := runner.Run(ctx, task); err != nil {
-               log.WithError(err).Error("failed to run task")
-       }
-}
+       cleanupRequestKey := client.SetRequestKey(requestKey)
+       defer cleanupRequestKey()
 
-func (p *poller) fetchTasks(ctx context.Context, client client.Client, 
tasksVersion *atomic.Int64, availableCapacity int64) ([]*runnerv1.Task, bool) {
        if availableCapacity == 0 {
-               return nil, false
+               return taskSlice, reuseRequestKey
        }
 
        reqCtx, cancel := context.WithTimeout(ctx, p.cfg.Runner.FetchTimeout)
@@ -192,27 +205,30 @@
                TaskCapacity: &availableCapacity,
        }))
        if errors.Is(err, context.DeadlineExceeded) {
-               log.Trace("deadline exceeded")
-               err = nil
-       }
-       if err != nil {
+               log.Trace("failed to fetch task: deadline exceeded")
+               return taskSlice, reuseRequestKey
+       } else if err != nil {
                if errors.Is(err, context.Canceled) {
                        log.WithError(err).Debugf("shutdown, fetch task 
canceled")
                } else {
                        log.WithError(err).Error("failed to fetch task")
                }
-               return nil, false
+               return taskSlice, reuseRequestKey
        }
 
        if resp == nil || resp.Msg == nil {
-               return nil, false
+               return taskSlice, reuseRequestKey
        }
 
+       // We've made a successful request without an error, regardless of 
whether we've received tasks or not, we now
+       // signal the calling loop that the request key should not be reused:
+       reuseRequestKey = false
+
        if resp.Msg.GetTasksVersion() > v {
                tasksVersion.CompareAndSwap(v, resp.Msg.GetTasksVersion())
        }
 
-       taskSlice := []*runnerv1.Task{}
+       taskSlice = []*runnerv1.Task{}
        // Normally we'd expect to get a Task, and maybe AdditionalTasks.  But 
we're resilient here to a bug in Forgejo
        // 14.0.0 & 14.0.1 where we might, rarely, get AdditionalTasks without 
a Task.
        if resp.Msg.Task != nil {
@@ -221,12 +237,12 @@
        taskSlice = append(taskSlice, resp.Msg.GetAdditionalTasks()...)
 
        if len(taskSlice) == 0 {
-               return nil, false
+               return taskSlice, reuseRequestKey
        } else if resp.Msg.Task == nil {
                log.Warn("FetchTask received tasks in AdditionalTasks field but 
not Task field; this is unexpected but runner will run them")
        }
 
        // got a task, set `tasksVersion` to zero to force query db in next 
request.
        tasksVersion.CompareAndSwap(resp.Msg.GetTasksVersion(), 0)
-       return taskSlice, true
+       return taskSlice, reuseRequestKey
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/forgejo-runner-12.7.0/internal/app/poll/poller_test.go 
new/forgejo-runner-12.7.2/internal/app/poll/poller_test.go
--- old/forgejo-runner-12.7.0/internal/app/poll/poller_test.go  2026-02-19 
02:28:46.000000000 +0100
+++ new/forgejo-runner-12.7.2/internal/app/poll/poller_test.go  2026-03-09 
17:04:00.000000000 +0100
@@ -5,7 +5,9 @@
 
 import (
        "context"
+       "errors"
        "fmt"
+       "sync"
        "sync/atomic"
        "testing"
        "testing/synctest"
@@ -22,6 +24,7 @@
        "code.forgejo.org/forgejo/runner/v12/internal/pkg/client/mocks"
        "code.forgejo.org/forgejo/runner/v12/internal/pkg/config"
 
+       gouuid "github.com/google/uuid"
        log "github.com/sirupsen/logrus"
        "github.com/stretchr/testify/assert"
        "github.com/stretchr/testify/mock"
@@ -59,6 +62,10 @@
        return time.Second
 }
 
+func (o mockClient) SetRequestKey(requestKey gouuid.UUID) func() {
+       return func() {}
+}
+
 func (o *mockClient) FetchTask(ctx context.Context, req 
*connect.Request[runnerv1.FetchTaskRequest]) 
(*connect.Response[runnerv1.FetchTaskResponse], error) {
        if o.sleep > 0 {
                select {
@@ -95,38 +102,19 @@
 }
 
 type mockRunner struct {
-       cfg    *config.Runner
-       log    chan string
-       panics bool
-       err    error
+       cfg *config.Runner
+       log chan string
 }
 
-func (o *mockRunner) Run(ctx context.Context, task *runnerv1.Task) error {
+func (o *mockRunner) Run(ctx context.Context, task *runnerv1.Task) {
        o.log <- "runner starts"
-       if o.panics {
-               log.Trace("panics")
-               o.log <- "runner panics"
-               o.panics = false
-               panic("whatever")
-       }
-       if o.err != nil {
-               log.Trace("error")
-               o.log <- "runner error"
-               err := o.err
-               o.err = nil
-               return err
-       }
-       for {
-               select {
-               case <-ctx.Done():
-                       log.Trace("shutdown")
-                       o.log <- "runner shutdown"
-                       return nil
-               case <-time.After(o.cfg.Timeout):
-                       log.Trace("after")
-                       o.log <- "runner timeout"
-                       return nil
-               }
+       select {
+       case <-ctx.Done():
+               log.Trace("shutdown")
+               o.log <- "runner shutdown"
+       case <-time.After(o.cfg.Timeout):
+               log.Trace("after")
+               o.log <- "runner timeout"
        }
 }
 
@@ -151,8 +139,6 @@
                name           string
                timeout        time.Duration
                noTask         bool
-               panics         bool
-               err            error
                expected       string
                contextTimeout time.Duration
        }{
@@ -162,18 +148,6 @@
                        expected: "runner shutdown",
                },
                {
-                       name:     "Panics",
-                       timeout:  10 * time.Second,
-                       panics:   true,
-                       expected: "runner panics",
-               },
-               {
-                       name:     "Error",
-                       timeout:  10 * time.Second,
-                       err:      fmt.Errorf("ERROR"),
-                       expected: "runner error",
-               },
-               {
                        name:     "PollTaskError",
                        timeout:  10 * time.Second,
                        noTask:   true,
@@ -202,10 +176,8 @@
                                        noTask: testCase.noTask,
                                }},
                                []run.RunnerInterface{&mockRunner{
-                                       cfg:    &configRunner,
-                                       log:    runnerLog,
-                                       panics: testCase.panics,
-                                       err:    testCase.err,
+                                       cfg: &configRunner,
+                                       log: runnerLog,
                                }})
                        go p.Poll()
                        assert.Equal(t, "runner starts", <-runnerLog)
@@ -251,8 +223,9 @@
                        cancel: true,
                },
                {
-                       name:   "NoTask",
-                       noTask: true,
+                       name:    "NoTask",
+                       success: true,
+                       noTask:  true,
                },
                {
                        name:      "AdditionalTasks",
@@ -291,13 +264,13 @@
                                }},
                                []run.RunnerInterface{&mockRunner{}},
                        )
-                       task, ok := p.fetchTasks(context.Background(), 
p.clients[0], &atomic.Int64{}, 100)
+                       task, reuseRequestKey := 
p.fetchTasks(context.Background(), p.clients[0], &atomic.Int64{}, 100, 
gouuid.New())
                        if testCase.success {
-                               assert.True(t, ok)
+                               assert.False(t, reuseRequestKey)
                                assert.NotNil(t, task)
                                assert.Len(t, task, testCase.taskCount)
                        } else {
-                               assert.False(t, ok)
+                               assert.True(t, reuseRequestKey)
                                assert.Nil(t, task)
                        }
                })
@@ -309,6 +282,7 @@
                mockClient := mocks.NewClient(t)
                mockClient.On("FetchInterval").Return(1 * time.Second)
                mockClient.On("Address").Return("https://client";)
+               mockClient.On("SetRequestKey", mock.Anything).Return(func() {})
                mockRunner := mock_runner.NewRunnerInterface(t)
                poller := New(pollingCtx, &config.Config{
                        Runner: config.Runner{
@@ -498,6 +472,7 @@
                mockClient := mocks.NewClient(t)
                mockClient.On("FetchInterval").Return(10 * time.Millisecond)
                mockClient.On("Address").Return("https://client";)
+               mockClient.On("SetRequestKey", mock.Anything).Return(func() {})
                mockRunner := mock_runner.NewRunnerInterface(t)
                poller := New(pollingCtx, &config.Config{
                        Runner: config.Runner{
@@ -601,9 +576,11 @@
                mockClient1 := mocks.NewClient(t)
                mockClient1.On("FetchInterval").Return(1 * time.Second)
                mockClient1.On("Address").Return("https://client1";)
+               mockClient1.On("SetRequestKey", mock.Anything).Return(func() {})
                mockClient2 := mocks.NewClient(t)
                mockClient2.On("FetchInterval").Return(30 * time.Second)
                mockClient2.On("Address").Return("https://client2";)
+               mockClient2.On("SetRequestKey", mock.Anything).Return(func() {})
                mockRunner := mock_runner.NewRunnerInterface(t)
                poller := New(pollingCtx, &config.Config{
                        Runner: config.Runner{
@@ -765,3 +742,112 @@
                })
        })
 }
+
+func TestPollerPollRequestKey(t *testing.T) {
+       setup := func(t *testing.T, pollingCtx context.Context) (*mocks.Client, 
*mock_runner.RunnerInterface, Poller) {
+               mockClient := mocks.NewClient(t)
+               mockClient.On("FetchInterval").Return(1 * time.Second)
+               mockClient.On("Address").Return("https://client";)
+               mockRunner := mock_runner.NewRunnerInterface(t)
+               poller := New(pollingCtx, &config.Config{
+                       Runner: config.Runner{
+                               Capacity: 3,
+                       },
+               }, []client.Client{mockClient}, 
[]run.RunnerInterface{mockRunner})
+               return mockClient, mockRunner, poller
+       }
+       teardown := func(t *testing.T, mockClient *mocks.Client, mockRunner 
*mock_runner.RunnerInterface) {
+               mockClient.AssertExpectations(t)
+               mockRunner.AssertExpectations(t)
+       }
+       emptyResponse := connect.NewResponse(&runnerv1.FetchTaskResponse{
+               Task:            nil,
+               TasksVersion:    int64(1),
+               AdditionalTasks: nil,
+       })
+
+       t.Run("each poll sets unique requestKey on client", func(t *testing.T) {
+               synctest.Test(t, func(t *testing.T) {
+                       pollingCtx, cancel := context.WithCancel(t.Context())
+                       defer cancel()
+
+                       var requestKeyMutex sync.Mutex // prevents this test 
from being flagged as a data race
+                       var requestKey1 gouuid.UUID
+                       var requestKey2 gouuid.UUID
+
+                       mockClient, mockRunner, poller := setup(t, pollingCtx)
+                       mockClient.On("FetchTask", mock.Anything, 
mock.Anything).Return(emptyResponse, nil)
+                       mockClient.On("SetRequestKey", mock.Anything).
+                               Once().
+                               Run(func(args mock.Arguments) {
+                                       requestKeyMutex.Lock()
+                                       requestKey1 = args.Get(0).(gouuid.UUID)
+                                       requestKeyMutex.Unlock()
+                               }).
+                               Return(func() {})
+                       mockClient.On("SetRequestKey", mock.Anything).
+                               Once().
+                               Run(func(args mock.Arguments) {
+                                       requestKeyMutex.Lock()
+                                       requestKey2 = args.Get(0).(gouuid.UUID)
+                                       requestKeyMutex.Unlock()
+                               }).
+                               Return(func() {})
+
+                       go poller.Poll()
+                       time.Sleep(1500 * time.Millisecond) // synctest delay 
to ensure two poll runs are complete
+
+                       requestKeyMutex.Lock()
+                       assert.NotEqualValues(t, 0, requestKey1.ID())           
             // invocation with a non-zero UUID
+                       assert.NotEqualValues(t, 0, requestKey2.ID())           
             // invocation with a non-zero UUID
+                       assert.NotEqualValues(t, requestKey1.String(), 
requestKey2.String()) // different UUIDs
+                       requestKeyMutex.Unlock()
+
+                       require.NoError(t, poller.Shutdown(t.Context()))
+                       teardown(t, mockClient, mockRunner)
+               })
+       })
+
+       t.Run("retains same requestKey on error", func(t *testing.T) {
+               synctest.Test(t, func(t *testing.T) {
+                       pollingCtx, cancel := context.WithCancel(t.Context())
+                       defer cancel()
+
+                       var requestKeyMutex sync.Mutex // prevents this test 
from being flagged as a data race
+                       var requestKey1 gouuid.UUID
+                       var requestKey2 gouuid.UUID
+
+                       mockClient, mockRunner, poller := setup(t, pollingCtx)
+                       mockClient.On("FetchTask", mock.Anything, 
mock.Anything).
+                               Return(nil, errors.New("some error"))
+                       mockClient.On("SetRequestKey", mock.Anything).
+                               Once().
+                               Run(func(args mock.Arguments) {
+                                       requestKeyMutex.Lock()
+                                       requestKey1 = args.Get(0).(gouuid.UUID)
+                                       requestKeyMutex.Unlock()
+                               }).
+                               Return(func() {})
+                       mockClient.On("SetRequestKey", mock.Anything).
+                               Once().
+                               Run(func(args mock.Arguments) {
+                                       requestKeyMutex.Lock()
+                                       requestKey2 = args.Get(0).(gouuid.UUID)
+                                       requestKeyMutex.Unlock()
+                               }).
+                               Return(func() {})
+
+                       go poller.Poll()
+                       time.Sleep(1500 * time.Millisecond) // synctest delay 
to ensure two poll runs are complete
+
+                       requestKeyMutex.Lock()
+                       assert.NotEqualValues(t, 0, requestKey1.ID())           
          // invocation with a non-zero UUID
+                       assert.NotEqualValues(t, 0, requestKey2.ID())           
          // invocation with a non-zero UUID
+                       assert.EqualValues(t, requestKey1.String(), 
requestKey2.String()) // same UUIDs
+                       requestKeyMutex.Unlock()
+
+                       require.NoError(t, poller.Shutdown(t.Context()))
+                       teardown(t, mockClient, mockRunner)
+               })
+       })
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/forgejo-runner-12.7.0/internal/app/run/mocks/RunnerInterface.go 
new/forgejo-runner-12.7.2/internal/app/run/mocks/RunnerInterface.go
--- old/forgejo-runner-12.7.0/internal/app/run/mocks/RunnerInterface.go 
2026-02-19 02:28:46.000000000 +0100
+++ new/forgejo-runner-12.7.2/internal/app/run/mocks/RunnerInterface.go 
2026-03-09 17:04:00.000000000 +0100
@@ -16,21 +16,8 @@
 }
 
 // Run provides a mock function with given fields: ctx, task
-func (_m *RunnerInterface) Run(ctx context.Context, task *runnerv1.Task) error 
{
-       ret := _m.Called(ctx, task)
-
-       if len(ret) == 0 {
-               panic("no return value specified for Run")
-       }
-
-       var r0 error
-       if rf, ok := ret.Get(0).(func(context.Context, *runnerv1.Task) error); 
ok {
-               r0 = rf(ctx, task)
-       } else {
-               r0 = ret.Error(0)
-       }
-
-       return r0
+func (_m *RunnerInterface) Run(ctx context.Context, task *runnerv1.Task) {
+       _m.Called(ctx, task)
 }
 
 // NewRunnerInterface creates a new instance of RunnerInterface. It also 
registers a testing interface on the mock and a cleanup function to assert the 
mocks expectations.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/forgejo-runner-12.7.0/internal/app/run/runner.go 
new/forgejo-runner-12.7.2/internal/app/run/runner.go
--- old/forgejo-runner-12.7.0/internal/app/run/runner.go        2026-02-19 
02:28:46.000000000 +0100
+++ new/forgejo-runner-12.7.2/internal/app/run/runner.go        2026-03-09 
17:04:00.000000000 +0100
@@ -51,7 +51,7 @@
 
 //go:generate mockery --name RunnerInterface
 type RunnerInterface interface {
-       Run(ctx context.Context, task *runnerv1.Task) error
+       Run(ctx context.Context, task *runnerv1.Task)
 }
 
 func NewRunner(cfg *config.Config, name string, ls labels.Labels, cli 
client.Client, cacheProxy *cacheproxy.Handler) *Runner {
@@ -150,11 +150,15 @@
        return cacheProxy
 }
 
-func (r *Runner) Run(ctx context.Context, task *runnerv1.Task) error {
-       if _, ok := r.runningTasks.Load(task.Id); ok {
-               return fmt.Errorf("task %d is already running", task.Id)
+func (r *Runner) Run(ctx context.Context, task *runnerv1.Task) {
+       if _, loaded := r.runningTasks.LoadOrStore(task.Id, struct{}{}); loaded 
{
+               // Normally the responsibility of Run() is to ensure that, no 
matter what occurs within running the job, a
+               // reporter is created that will report the job status to the 
server. But in this case, for some reason another
+               // goroutine is currently running this exact same task on this 
same runner -- we can't report status in this
+               // case because it would corrupt the status of the other 
running job.
+               log.Errorf("task %d is already running", task.Id)
+               return
        }
-       r.runningTasks.Store(task.Id, struct{}{})
        defer r.runningTasks.Delete(task.Id)
 
        ctx, cancel := context.WithTimeout(ctx, r.cfg.Runner.Timeout)
@@ -169,8 +173,6 @@
        }()
        reporter.RunDaemon()
        runErr = r.run(ctx, task, reporter)
-
-       return nil
 }
 
 func logAndReport(reporter *report.Reporter, message string, args ...any) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/forgejo-runner-12.7.0/internal/app/run/runner_test.go 
new/forgejo-runner-12.7.2/internal/app/run/runner_test.go
--- old/forgejo-runner-12.7.0/internal/app/run/runner_test.go   2026-02-19 
02:28:46.000000000 +0100
+++ new/forgejo-runner-12.7.2/internal/app/run/runner_test.go   2026-03-09 
17:04:00.000000000 +0100
@@ -20,6 +20,7 @@
        "code.forgejo.org/forgejo/runner/v12/internal/pkg/labels"
        "code.forgejo.org/forgejo/runner/v12/internal/pkg/report"
        "connectrpc.com/connect"
+       gouuid "github.com/google/uuid"
        log "github.com/sirupsen/logrus"
        "google.golang.org/protobuf/types/known/structpb"
        "gotest.tools/v3/skip"
@@ -95,6 +96,11 @@
        return time.Duration(args.Int(0))
 }
 
+func (m *forgejoClientMock) SetRequestKey(requestKey gouuid.UUID) func() {
+       args := m.Called(requestKey)
+       return args.Get(0).(func())
+}
+
 func (m *forgejoClientMock) Ping(ctx context.Context, request 
*connect.Request[pingv1.PingRequest]) (*connect.Response[pingv1.PingResponse], 
error) {
        args := m.Called(ctx, request)
        if args.Get(0) == nil {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/forgejo-runner-12.7.0/internal/pkg/client/client.go 
new/forgejo-runner-12.7.2/internal/pkg/client/client.go
--- old/forgejo-runner-12.7.0/internal/pkg/client/client.go     2026-02-19 
02:28:46.000000000 +0100
+++ new/forgejo-runner-12.7.2/internal/pkg/client/client.go     2026-03-09 
17:04:00.000000000 +0100
@@ -8,6 +8,7 @@
 
        "code.forgejo.org/forgejo/actions-proto/ping/v1/pingv1connect"
        "code.forgejo.org/forgejo/actions-proto/runner/v1/runnerv1connect"
+       gouuid "github.com/google/uuid"
 )
 
 // A Client manages communication with the runner.
@@ -19,4 +20,5 @@
        Address() string
        Insecure() bool
        FetchInterval() time.Duration
+       SetRequestKey(gouuid.UUID) func()
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/forgejo-runner-12.7.0/internal/pkg/client/header.go 
new/forgejo-runner-12.7.2/internal/pkg/client/header.go
--- old/forgejo-runner-12.7.0/internal/pkg/client/header.go     2026-02-19 
02:28:46.000000000 +0100
+++ new/forgejo-runner-12.7.2/internal/pkg/client/header.go     2026-03-09 
17:04:00.000000000 +0100
@@ -4,6 +4,7 @@
 package client
 
 const (
-       UUIDHeader  = "x-runner-uuid"
-       TokenHeader = "x-runner-token"
+       UUIDHeader       = "x-runner-uuid"
+       TokenHeader      = "x-runner-token"
+       RequestKeyHeader = "x-runner-request-key"
 )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/forgejo-runner-12.7.0/internal/pkg/client/http.go 
new/forgejo-runner-12.7.2/internal/pkg/client/http.go
--- old/forgejo-runner-12.7.0/internal/pkg/client/http.go       2026-02-19 
02:28:46.000000000 +0100
+++ new/forgejo-runner-12.7.2/internal/pkg/client/http.go       2026-03-09 
17:04:00.000000000 +0100
@@ -15,6 +15,7 @@
        "code.forgejo.org/forgejo/actions-proto/ping/v1/pingv1connect"
        "code.forgejo.org/forgejo/actions-proto/runner/v1/runnerv1connect"
        "connectrpc.com/connect"
+       gouuid "github.com/google/uuid"
        log "github.com/sirupsen/logrus"
 )
 
@@ -55,6 +56,12 @@
 func New(endpoint string, insecure bool, uuid, token, version string, 
fetchInterval time.Duration, opts ...connect.ClientOption) *HTTPClient {
        baseURL := strings.TrimRight(endpoint, "/") + "/api/actions"
 
+       client := &HTTPClient{
+               endpoint:      endpoint,
+               insecure:      insecure,
+               fetchInterval: fetchInterval,
+       }
+
        opts = append(opts, 
connect.WithInterceptors(connect.UnaryInterceptorFunc(func(next 
connect.UnaryFunc) connect.UnaryFunc {
                return func(ctx context.Context, req connect.AnyRequest) 
(connect.AnyResponse, error) {
                        if uuid != "" {
@@ -63,25 +70,25 @@
                        if token != "" {
                                req.Header().Set(TokenHeader, token)
                        }
+                       if client.requestKey != nil {
+                               req.Header().Set(RequestKeyHeader, 
client.requestKey.String())
+                       }
                        return next(ctx, req)
                }
        })))
 
-       return &HTTPClient{
-               PingServiceClient: pingv1connect.NewPingServiceClient(
-                       getHTTPClient(endpoint, insecure),
-                       baseURL,
-                       opts...,
-               ),
-               RunnerServiceClient: runnerv1connect.NewRunnerServiceClient(
-                       getHTTPClient(endpoint, insecure),
-                       baseURL,
-                       opts...,
-               ),
-               endpoint:      endpoint,
-               insecure:      insecure,
-               fetchInterval: fetchInterval,
-       }
+       client.PingServiceClient = pingv1connect.NewPingServiceClient(
+               getHTTPClient(endpoint, insecure),
+               baseURL,
+               opts...,
+       )
+       client.RunnerServiceClient = runnerv1connect.NewRunnerServiceClient(
+               getHTTPClient(endpoint, insecure),
+               baseURL,
+               opts...,
+       )
+
+       return client
 }
 
 func (c *HTTPClient) Address() string {
@@ -96,6 +103,13 @@
        return c.fetchInterval
 }
 
+func (c *HTTPClient) SetRequestKey(uuid gouuid.UUID) func() {
+       c.requestKey = &uuid
+       return func() {
+               c.requestKey = nil
+       }
+}
+
 var _ Client = (*HTTPClient)(nil)
 
 // An HTTPClient manages communication with the runner API.
@@ -105,4 +119,5 @@
        endpoint      string
        insecure      bool
        fetchInterval time.Duration
+       requestKey    *gouuid.UUID
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/forgejo-runner-12.7.0/internal/pkg/client/mocks/Client.go 
new/forgejo-runner-12.7.2/internal/pkg/client/mocks/Client.go
--- old/forgejo-runner-12.7.0/internal/pkg/client/mocks/Client.go       
2026-02-19 02:28:46.000000000 +0100
+++ new/forgejo-runner-12.7.2/internal/pkg/client/mocks/Client.go       
2026-03-09 17:04:00.000000000 +0100
@@ -14,6 +14,8 @@
        runnerv1 "code.forgejo.org/forgejo/actions-proto/runner/v1"
 
        time "time"
+
+       uuid "github.com/google/uuid"
 )
 
 // Client is an autogenerated mock type for the Client type
@@ -195,6 +197,26 @@
        return r0, r1
 }
 
+// SetRequestKey provides a mock function with given fields: _a0
+func (_m *Client) SetRequestKey(_a0 uuid.UUID) func() {
+       ret := _m.Called(_a0)
+
+       if len(ret) == 0 {
+               panic("no return value specified for SetRequestKey")
+       }
+
+       var r0 func()
+       if rf, ok := ret.Get(0).(func(uuid.UUID) func()); ok {
+               r0 = rf(_a0)
+       } else {
+               if ret.Get(0) != nil {
+                       r0 = ret.Get(0).(func())
+               }
+       }
+
+       return r0
+}
+
 // UpdateLog provides a mock function with given fields: _a0, _a1
 func (_m *Client) UpdateLog(_a0 context.Context, _a1 
*connect.Request[runnerv1.UpdateLogRequest]) 
(*connect.Response[runnerv1.UpdateLogResponse], error) {
        ret := _m.Called(_a0, _a1)

++++++ forgejo-runner.obsinfo ++++++
--- /var/tmp/diff_new_pack.QJKNVP/_old  2026-03-17 19:07:35.184352160 +0100
+++ /var/tmp/diff_new_pack.QJKNVP/_new  2026-03-17 19:07:35.196352657 +0100
@@ -1,5 +1,5 @@
 name: forgejo-runner
-version: 12.7.0
-mtime: 1771464526
-commit: bb4d56ff714d68e11fa67b9a975fb7de3d6b38cc
+version: 12.7.2
+mtime: 1773072240
+commit: 7d42cbf894cb44b28171604c1fd7e9e2cc927540
 

++++++ vendor.tar.gz ++++++
++++ 48687 lines of diff (skipped)

Reply via email to