bug: --container-daemon-socket parameter does not apply to runner #577

Closed
opened 2025-05-26 00:38:41 +00:00 by fiocobbs · 8 comments

Can you reproduce the bug on the Forgejo test instance?

N/A

Description

Passing --container-daemon-socket into the ./forgejo-runner executable doesn't seem to affect the utilized socket. It will instead default to unix:///var/run/docker.sock, and since I'm using podman with the socket path being unix://$XDG_RUNTIME_DIR/podman/podman.sock, it will fail. nektos/act does not have this issue. See logs for the command used to test.

I've managed to make a working build for myself, however, I am very not confident it should be merged as-is, for the following reasons:

  • I'm completely oblivious with the environment variable handling system, and I'm worried I may have caused a regression with my code
  • I don't know if tracking the extra file(s) is a good idea with a separate repo, and not sure if it'd just be better to inline the function within exec.go
  • My setup is currently broken as of editing, after I've removed the --container-daemon-socket and tried adding it back in. Looks like the env vars are mutable?
    • I genuinely have no idea how it happened, but a /var/lib/docker.sock was created as a folder, which would not be a valid host. Not sure if the following code patch for docker_socket.go would be warranted to add, but maybe?

      // returns socket URI or false if not found any
      func socketLocation() (string, bool) {
      	if dockerHost, exists := os.LookupEnv("DOCKER_HOST"); exists {
      		log.Infof("oijewfoiwfj")
      		return dockerHost, true
      	}
      
      	for _, p := range CommonSocketLocations {
      		if file, err := os.Lstat(os.ExpandEnv(p)); err == nil {
      			// START NEW CODE
      			if file.IsDir() {
      				log.Warnf("socket \"%s\" doesn't seem like a socket, continuing scan...", p)
      				continue
      			}
      			// END NEW CODE
      
      			if strings.HasPrefix(p, `\\.\`) {
      				return "npipe://" + filepath.ToSlash(os.ExpandEnv(p)), true
      			}
      			log.Infof("p %s", p)
      			log.Infof("filepath %s", filepath.ToSlash(os.ExpandEnv(p)))
      			return "unix://" + filepath.ToSlash(os.ExpandEnv(p)), true
      		}
      	}
      
      	return "", false
      }
      
      

If I could get clarification though on what should be done though, I'd be happy to create the two PRs.

The patch relies on copying the docker_socket.go from upstream into the containers repo, and copies some code from nektos/act/root.go.

runner repository Diff
diff --git a/internal/app/cmd/exec.go b/internal/app/cmd/exec.go
index 404f4ae..c06f738 100644
--- a/internal/app/cmd/exec.go
+++ b/internal/app/cmd/exec.go
@@ -13,11 +13,12 @@ import (
 	"strings"
 	"time"
 
-	"github.com/docker/docker/api/types/container"
+	docker_container "github.com/docker/docker/api/types/container"
 	"github.com/joho/godotenv"
 	"github.com/nektos/act/pkg/artifactcache"
 	"github.com/nektos/act/pkg/artifacts"
 	"github.com/nektos/act/pkg/common"
+	"github.com/nektos/act/pkg/container"
 	"github.com/nektos/act/pkg/model"
 	"github.com/nektos/act/pkg/runner"
 	log "github.com/sirupsen/logrus"
@@ -377,6 +378,15 @@ func runExec(ctx context.Context, execArgs *executeArgs) func(cmd *cobra.Command
 			execArgs.artifactServerPath = tempDir
 		}
 
+		// Potentially-uninformed hack to get DOCKER_HOST to work
+		if ret, err := container.GetSocketAndHost(execArgs.containerDaemonSocket); err != nil {
+			log.Warnf("Couldn't get a valid docker connection: %+v", err)
+		} else {
+			os.Setenv("DOCKER_HOST", ret.Host)
+			execArgs.containerDaemonSocket = ret.Socket
+			log.Infof("Using docker host '%s', and daemon socket '%s'", ret.Host, ret.Socket)
+		}
+
 		// run the plan
 		config := &runner.Config{
 			Workdir:               execArgs.Workdir(),
@@ -407,7 +417,7 @@ func runExec(ctx context.Context, execArgs *executeArgs) func(cmd *cobra.Command
 			// EventJSON:                  string(eventJSON),
 			ContainerNamePrefix:        fmt.Sprintf("FORGEJO-ACTIONS-TASK-%s", eventName),
 			ContainerMaxLifetime:       maxLifetime,
-			ContainerNetworkMode:       container.NetworkMode(execArgs.network),
+			ContainerNetworkMode:       docker_container.NetworkMode(execArgs.network),
 			ContainerNetworkEnableIPv6: execArgs.enableIPv6,
 			DefaultActionInstance:      execArgs.defaultActionsURL,
 			PlatformPicker: func(_ []string) string {

I also believe passing in a DOCKER_HOST via CLI doesn't do anything, but this is worth sanity-checking.

Forgejo Version

N/A

Runner Version

6245f71556

How are you running Forgejo?

Attempting to use forgejo-runner as a standalone CLI, to test before pushing to a Forgejo instance

How are you running the Runner?

Compiled from source, at the commit hash defined.

  • NixOS 25.05 (Warbler) x86_64
  • podman version 5.4.1

Logs

./forgejo-runner exec --container-daemon-socket unix://$XDG_RUNTIME_DIR/podman/podman.sock

INFO[0000] Using default workflow event: push           
INFO[0000] Planning jobs for event: push                
INFO[0000] cache handler listens on: http://10.8.0.7:46267 
INFO[0000] Start server on http://10.8.0.7:34567        
[build.yaml/Build] [DEBUG] evaluating expression 'success()'
[build.yaml/Build] [DEBUG] expression 'success()' evaluated to 'true'
[build.yaml/Build] 🚀  Start image=debian:12
INFO[0000] Parallel tasks (0) below minimum, setting to 1 
[build.yaml/Build]   🐳  docker pull image=debian:12 platform= username= forcePull=false
[build.yaml/Build] [DEBUG]   🐳  docker pull debian:12
[build.yaml/Build] [DEBUG] Image exists? false
Error: unable to determine if image already exists for image 'debian:12' (): Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?

Workflow file

No response

### Can you reproduce the bug on the Forgejo test instance? *N/A* ### Description Passing `--container-daemon-socket` into the `./forgejo-runner` executable doesn't seem to affect the utilized socket. It will instead default to `unix:///var/run/docker.sock`, and since I'm using podman with the socket path being `unix://$XDG_RUNTIME_DIR/podman/podman.sock`, it will fail. [nektos/act](https://github.com/nektos/act) does not have this issue. See logs for the command used to test. I've managed to make a working build for myself, however, I am very not confident it should be merged as-is, for the following reasons: - I'm completely oblivious with the environment variable handling system, and I'm worried I may have caused a regression with my code - I don't know if tracking the extra file(s) is a good idea with a separate repo, and not sure if it'd just be better to inline the function within `exec.go` - My setup is currently broken as of editing, after I've removed the `--container-daemon-socket` and tried adding it back in. ~~Looks like the env vars are mutable?~~ - I genuinely have no idea how it happened, but a `/var/lib/docker.sock` was created as a folder, which would not be a valid host. Not sure if the following code patch for `docker_socket.go` would be warranted to add, but maybe? <details> ```go // returns socket URI or false if not found any func socketLocation() (string, bool) { if dockerHost, exists := os.LookupEnv("DOCKER_HOST"); exists { log.Infof("oijewfoiwfj") return dockerHost, true } for _, p := range CommonSocketLocations { if file, err := os.Lstat(os.ExpandEnv(p)); err == nil { // START NEW CODE if file.IsDir() { log.Warnf("socket \"%s\" doesn't seem like a socket, continuing scan...", p) continue } // END NEW CODE if strings.HasPrefix(p, `\\.\`) { return "npipe://" + filepath.ToSlash(os.ExpandEnv(p)), true } log.Infof("p %s", p) log.Infof("filepath %s", filepath.ToSlash(os.ExpandEnv(p))) return "unix://" + filepath.ToSlash(os.ExpandEnv(p)), true } } return "", false } ``` </details> If I could get clarification though on what should be done though, I'd be happy to create the two PRs. The patch relies on copying the [docker_socket.go](https://github.com/nektos/act/blob/master/pkg/container/docker_socket.go) from upstream into the containers repo, and copies some code from [nektos/act/root.go](https://github.com/nektos/act/blob/a8ac5293447a2412228f45cf3a430dcd1e92951f/cmd/root.go#L406C1-L412C4). <details><summary>runner repository Diff</summary> ```diff diff --git a/internal/app/cmd/exec.go b/internal/app/cmd/exec.go index 404f4ae..c06f738 100644 --- a/internal/app/cmd/exec.go +++ b/internal/app/cmd/exec.go @@ -13,11 +13,12 @@ import ( "strings" "time" - "github.com/docker/docker/api/types/container" + docker_container "github.com/docker/docker/api/types/container" "github.com/joho/godotenv" "github.com/nektos/act/pkg/artifactcache" "github.com/nektos/act/pkg/artifacts" "github.com/nektos/act/pkg/common" + "github.com/nektos/act/pkg/container" "github.com/nektos/act/pkg/model" "github.com/nektos/act/pkg/runner" log "github.com/sirupsen/logrus" @@ -377,6 +378,15 @@ func runExec(ctx context.Context, execArgs *executeArgs) func(cmd *cobra.Command execArgs.artifactServerPath = tempDir } + // Potentially-uninformed hack to get DOCKER_HOST to work + if ret, err := container.GetSocketAndHost(execArgs.containerDaemonSocket); err != nil { + log.Warnf("Couldn't get a valid docker connection: %+v", err) + } else { + os.Setenv("DOCKER_HOST", ret.Host) + execArgs.containerDaemonSocket = ret.Socket + log.Infof("Using docker host '%s', and daemon socket '%s'", ret.Host, ret.Socket) + } + // run the plan config := &runner.Config{ Workdir: execArgs.Workdir(), @@ -407,7 +417,7 @@ func runExec(ctx context.Context, execArgs *executeArgs) func(cmd *cobra.Command // EventJSON: string(eventJSON), ContainerNamePrefix: fmt.Sprintf("FORGEJO-ACTIONS-TASK-%s", eventName), ContainerMaxLifetime: maxLifetime, - ContainerNetworkMode: container.NetworkMode(execArgs.network), + ContainerNetworkMode: docker_container.NetworkMode(execArgs.network), ContainerNetworkEnableIPv6: execArgs.enableIPv6, DefaultActionInstance: execArgs.defaultActionsURL, PlatformPicker: func(_ []string) string { ``` </details> I also believe passing in a `DOCKER_HOST` via CLI doesn't do anything, but this is worth sanity-checking. ### Forgejo Version N/A ### Runner Version 6245f71556178342f0163cd88d0a0070f1eabf53 ### How are you running Forgejo? Attempting to use forgejo-runner as a standalone CLI, to test before pushing to a Forgejo instance ### How are you running the Runner? Compiled from source, at the commit hash defined. - NixOS 25.05 (Warbler) x86_64 - podman version 5.4.1 ### Logs `./forgejo-runner exec --container-daemon-socket unix://$XDG_RUNTIME_DIR/podman/podman.sock` ``` INFO[0000] Using default workflow event: push INFO[0000] Planning jobs for event: push INFO[0000] cache handler listens on: http://10.8.0.7:46267 INFO[0000] Start server on http://10.8.0.7:34567 [build.yaml/Build] [DEBUG] evaluating expression 'success()' [build.yaml/Build] [DEBUG] expression 'success()' evaluated to 'true' [build.yaml/Build] 🚀 Start image=debian:12 INFO[0000] Parallel tasks (0) below minimum, setting to 1 [build.yaml/Build] 🐳 docker pull image=debian:12 platform= username= forcePull=false [build.yaml/Build] [DEBUG] 🐳 docker pull debian:12 [build.yaml/Build] [DEBUG] Image exists? false Error: unable to determine if image already exists for image 'debian:12' (): Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running? ``` ### Workflow file _No response_
fiocobbs changed title from bug: --container-daemon-socket parameter does not apply to runner to bug: --container-daemon-socket parameter does not apply to runner 2025-05-26 00:51:59 +00:00
Contributor

Passing --container-daemon-socket into the ./forgejo-runner executable doesn't seem to affect the utilized socket. It will instead default to unix:///var/run/docker.sock,

I would not be surprised if the code handling variables overrides the parameter.

I did not look closely enough to be sure but from a distance it looks like you are on the right track. If you send a PR to fix this I'll take a closer look and this can be fixed on the next release.

Note that there are assorted tests at https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions (example-with-docker-host etc.) and one could be added to verify the argument is processed as it should. Alternatively the argument could be removed for simplification, unless it is a blocker.

> Passing --container-daemon-socket into the ./forgejo-runner executable doesn't seem to affect the utilized socket. It will instead default to unix:///var/run/docker.sock, I would not be surprised if the code handling variables overrides the parameter. I did not look closely enough to be sure but from a distance it looks like you are on the right track. If you send a PR to fix this I'll take a closer look and this can be fixed on the next release. Note that there are assorted tests at https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions (example-with-docker-host etc.) and one could be added to verify the argument is processed as it should. Alternatively the argument could be removed for simplification, unless it is a blocker.

@fiocobbs wrote in #577 (comment):

Passing --container-daemon-socket into the ./forgejo-runner executable doesn't seem to affect the utilized socket. It will instead default to unix:///var/run/docker.sock,

Just install the podman-docker package. It will provide a link reference ....

$ cat /usr/lib/tmpfiles.d/podman-docker.conf
L+  %t/docker.sock   -    -    -     -   %t/podman/podman.sock
@fiocobbs wrote in https://code.forgejo.org/forgejo/runner/issues/577#issue-7252: > ### > > Passing `--container-daemon-socket` into the `./forgejo-runner` executable doesn't seem to affect the utilized socket. It will instead default to `unix:///var/run/docker.sock`, Just install the podman-docker package. It will provide a link reference .... ``` $ cat /usr/lib/tmpfiles.d/podman-docker.conf L+ %t/docker.sock - - - - %t/podman/podman.sock ```

@el0n wrote in #577 (comment):

@fiocobbs wrote in #577 (comment):

Passing --container-daemon-socket into the ./forgejo-runner executable doesn't seem to affect the utilized socket. It will instead default to unix:///var/run/docker.sock,

Just install the podman-docker package. It will provide a link reference ....

$ cat /usr/lib/tmpfiles.d/podman-docker.conf
L+  %t/docker.sock   -    -    -     -   %t/podman/podman.sock

Unfortunately this doesn't "fix" the issue since podman can run in both "rootfull" and "rootless" mode. The latter the socket is located at unix://$XDG_RUNTIME_DIR/podman/podman.sock , which for example on my machine translates to unix:///run/user/1000/podman/podman.sock while /var/run/docker.sock is a symlink to /run/podman/podman.sock and /run/podman have 0700 permission set which the user have no access, unless you run sudo forgejo-runner .... which any best practices will say otherwise.

I would rather see a fix to use either DOCKER_HOST or DOCKER_SOCKET env variables

@el0n wrote in https://code.forgejo.org/forgejo/runner/issues/577#issuecomment-41034: > @fiocobbs wrote in #577 (comment): > > > ### [](#heading) > > Passing `--container-daemon-socket` into the `./forgejo-runner` executable doesn't seem to affect the utilized socket. It will instead default to `unix:///var/run/docker.sock`, > > Just install the podman-docker package. It will provide a link reference .... > > ```text > $ cat /usr/lib/tmpfiles.d/podman-docker.conf > L+ %t/docker.sock - - - - %t/podman/podman.sock > ``` Unfortunately this doesn't "fix" the issue since podman can run in both "rootfull" and "rootless" mode. The latter the socket is located at `unix://$XDG_RUNTIME_DIR/podman/podman.sock` , which for example on my machine translates to `unix:///run/user/1000/podman/podman.sock` while `/var/run/docker.sock` is a symlink to `/run/podman/podman.sock` and `/run/podman` have `0700` permission set which the user have no access, unless you run `sudo forgejo-runner ....` which any best practices will say otherwise. I would rather see a fix to use either DOCKER_HOST or DOCKER_SOCKET env variables

What matters is how it looks like in the container. Here a snippet of a user quadlet file:
Volume=%t/podman/podman.sock:/var/run/docker.sock

What matters is how it looks like in the container. Here a snippet of a user quadlet file: `Volume=%t/podman/podman.sock:/var/run/docker.sock`
Contributor

@fiocobbs @c8h4 there are two pull requests in the same area at the moment:

They were rebased against the latest main branch today and pass tests (no big surprise 😁). I'll comment here since the review applies to both.

The logic of how the container server is determined in Forgejo runner is convoluted. And it turns out the --container-daemon-socket does not do anything, because it is always overridden by the existing logic.

Showcase

on: [push]
jobs:
  test:
    runs-on: docker
    steps:
      - run: echo All good!
earl-warren:/tmp/g$ forgejo-runner-9.0.3 exec --workflows /tmp/a.yml
INFO[0000] Using the only detected workflow event: push 
INFO[0000] Planning jobs for event: push                
INFO[0000] cache handler listens on: http://192.168.3.6:39645 
[a.yml/test] 🚀  Start image=node:20-bullseye
[a.yml/test]   🐳  docker pull image=node:20-bullseye platform= username= forcePull=false
[a.yml/test] Cleaning up network for job test, and network name is: FORGEJO-ACTIONS-TASK-push_WORKFLOW-40eb3fd3849866be6240d5d99e801ba8be6003ba7e2f976b44388af58dedd5a0_JOB-test-test-network
[a.yml/test]   🐳  docker create image=node:20-bullseye platform= entrypoint=["tail" "-f" "/dev/null"] cmd=[] network="FORGEJO-ACTIONS-TASK-push_WORKFLOW-40eb3fd3849866be6240d5d99e801ba8be6003ba7e2f976b44388af58dedd5a0_JOB-test-test-network"
[a.yml/test]   🐳  docker run image=node:20-bullseye platform= entrypoint=["tail" "-f" "/dev/null"] cmd=[] network="FORGEJO-ACTIONS-TASK-push_WORKFLOW-40eb3fd3849866be6240d5d99e801ba8be6003ba7e2f976b44388af58dedd5a0_JOB-test-test-network"
[a.yml/test] ⭐ Run Main echo All good!
[a.yml/test]   🐳  docker exec cmd=[sh /var/run/act/b072be7d355cfb3b.sh] user= workdir=
[a.yml/test]   🐳  docker exec cmd=[bash --noprofile --norc -e -o pipefail /var/run/act/workflow/0.sh] user= workdir=
| All good!
[a.yml/test]   ✅  Success - Main echo All good!
[a.yml/test] 🏁  Job succeeded
INFO[0001] Cleaning up network for job test, and network name is: FORGEJO-ACTIONS-TASK-push_WORKFLOW-40eb3fd3849866be6240d5d99e801ba8be6003ba7e2f976b44388af58dedd5a0_JOB-test-test-network 

It still works fine with forgejo-runner-9.0.3 exec --container-daemon-socket=/aaa --workflows /tmp/a.yml. But it breaks with:

earl-warren:/tmp/g$ DOCKER_HOST=/aaaa forgejo-runner-9.0.3 exec --workflows /tmp/a.yml
INFO[0000] Using the only detected workflow event: push 
INFO[0000] Planning jobs for event: push                
INFO[0000] cache handler listens on: http://192.168.3.6:35655 
[a.yml/test] 🚀  Start image=node:20-bullseye
[a.yml/test]   🐳  docker pull image=node:20-bullseye platform= username= forcePull=false
Error: unable to determine if image already exists for image 'node:20-bullseye' (): failed to connect to docker daemon: unable to parse docker host `/aaaa`

Instead of trying to fix it and since there is already a way to specify the desired value from the environment, I think the option should just be removed. The variable DOCKER_HOST is documented in a specific case. But it may be good to add a line about it in the help of forgejo-runner exec.

What do you think?

@fiocobbs @c8h4 there are two pull requests in the same area at the moment: - https://code.forgejo.org/forgejo/runner/pulls/598 - https://code.forgejo.org/forgejo/runner/pulls/581 They were rebased against the latest main branch today and pass tests (no big surprise 😁). I'll comment here since the review applies to both. The logic of how the container server is determined in Forgejo runner is convoluted. And it turns out the `--container-daemon-socket` does not do anything, because it is always overridden by the existing logic. ### Showcase ```yaml on: [push] jobs: test: runs-on: docker steps: - run: echo All good! ``` ```sh earl-warren:/tmp/g$ forgejo-runner-9.0.3 exec --workflows /tmp/a.yml INFO[0000] Using the only detected workflow event: push INFO[0000] Planning jobs for event: push INFO[0000] cache handler listens on: http://192.168.3.6:39645 [a.yml/test] 🚀 Start image=node:20-bullseye [a.yml/test] 🐳 docker pull image=node:20-bullseye platform= username= forcePull=false [a.yml/test] Cleaning up network for job test, and network name is: FORGEJO-ACTIONS-TASK-push_WORKFLOW-40eb3fd3849866be6240d5d99e801ba8be6003ba7e2f976b44388af58dedd5a0_JOB-test-test-network [a.yml/test] 🐳 docker create image=node:20-bullseye platform= entrypoint=["tail" "-f" "/dev/null"] cmd=[] network="FORGEJO-ACTIONS-TASK-push_WORKFLOW-40eb3fd3849866be6240d5d99e801ba8be6003ba7e2f976b44388af58dedd5a0_JOB-test-test-network" [a.yml/test] 🐳 docker run image=node:20-bullseye platform= entrypoint=["tail" "-f" "/dev/null"] cmd=[] network="FORGEJO-ACTIONS-TASK-push_WORKFLOW-40eb3fd3849866be6240d5d99e801ba8be6003ba7e2f976b44388af58dedd5a0_JOB-test-test-network" [a.yml/test] ⭐ Run Main echo All good! [a.yml/test] 🐳 docker exec cmd=[sh /var/run/act/b072be7d355cfb3b.sh] user= workdir= [a.yml/test] 🐳 docker exec cmd=[bash --noprofile --norc -e -o pipefail /var/run/act/workflow/0.sh] user= workdir= | All good! [a.yml/test] ✅ Success - Main echo All good! [a.yml/test] 🏁 Job succeeded INFO[0001] Cleaning up network for job test, and network name is: FORGEJO-ACTIONS-TASK-push_WORKFLOW-40eb3fd3849866be6240d5d99e801ba8be6003ba7e2f976b44388af58dedd5a0_JOB-test-test-network ``` It still works fine with `forgejo-runner-9.0.3 exec --container-daemon-socket=/aaa --workflows /tmp/a.yml`. But it breaks with: ```sh earl-warren:/tmp/g$ DOCKER_HOST=/aaaa forgejo-runner-9.0.3 exec --workflows /tmp/a.yml INFO[0000] Using the only detected workflow event: push INFO[0000] Planning jobs for event: push INFO[0000] cache handler listens on: http://192.168.3.6:35655 [a.yml/test] 🚀 Start image=node:20-bullseye [a.yml/test] 🐳 docker pull image=node:20-bullseye platform= username= forcePull=false Error: unable to determine if image already exists for image 'node:20-bullseye' (): failed to connect to docker daemon: unable to parse docker host `/aaaa` ``` Instead of trying to fix it and since there is already a way to specify the desired value from the environment, I think the option should just be removed. The variable `DOCKER_HOST` is [documented](https://forgejo.org/docs/next/admin/actions/runner-installation/#setting-up-the-container-environment) in a specific case. But it may be good to add a line about it in the help of `forgejo-runner exec`. What do you think?
Contributor

@fiocobbs @c8h4 if you would like me to try and fix this please let me know. Otherwise it can wait until you have time. 🙏

@fiocobbs @c8h4 if you would like me to try and fix this please let me know. Otherwise it can wait until you have time. 🙏
Contributor

Instead of trying to fix it and since there is already a way to specify the desired value from the environment, I think the option should just be removed. The variable DOCKER_HOST is documented in a specific case. But it may be good to add a line about it in the help of forgejo-runner exec.

I went ahead and did that at #1004

> Instead of trying to fix it and since there is already a way to specify the desired value from the environment, I think the option should just be removed. The variable DOCKER_HOST is documented in a specific case. But it may be good to add a line about it in the help of forgejo-runner exec. I went ahead and did that at https://code.forgejo.org/forgejo/runner/pulls/1004
Contributor

The scope of the proposed pull request was too wide, I'll submit another.

The scope of the proposed pull request was too wide, I'll submit another.
Sign in to join this conversation.
No milestone
No assignees
4 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
forgejo/runner#577
No description provided.