diff --git a/cmd/forgejo/actions.go b/cmd/forgejo/actions.go
index 58accdc8b7..2fb72353db 100644
--- a/cmd/forgejo/actions.go
+++ b/cmd/forgejo/actions.go
@@ -14,6 +14,7 @@ import (
 	actions_model "code.gitea.io/gitea/models/actions"
 	"code.gitea.io/gitea/modules/private"
 	"code.gitea.io/gitea/modules/setting"
+	private_routers "code.gitea.io/gitea/routers/private"
 
 	"github.com/urfave/cli"
 )
@@ -129,10 +130,14 @@ func validateSecret(secret string) error {
 }
 
 func RunRegister(ctx context.Context, cliCtx *cli.Context) error {
-	if !ContextGetNoInstallSignals(ctx) {
+	if !ContextGetNoInit(ctx) {
 		var cancel context.CancelFunc
 		ctx, cancel = installSignals(ctx)
 		defer cancel()
+
+		if err := initDB(ctx); err != nil {
+			return err
+		}
 	}
 	setting.MustInstalled()
 
@@ -165,12 +170,17 @@ func RunRegister(ctx context.Context, cliCtx *cli.Context) error {
 	// the internal naming. It is still confusing to the developer but
 	// not to the user.
 	//
-	respText, extra := private.ActionsRunnerRegister(ctx, secret, scope, strings.Split(labels, ","), name, version)
-	if extra.HasError() {
-		return handleCliResponseExtra(ctx, extra)
+	owner, repo, err := private_routers.ParseScope(ctx, scope)
+	if err != nil {
+		return err
 	}
 
-	if _, err := fmt.Fprintf(ContextGetStdout(ctx), "%s", respText); err != nil {
+	runner, err := actions_model.RegisterRunner(ctx, owner, repo, secret, strings.Split(labels, ","), name, version)
+	if err != nil {
+		return fmt.Errorf("error while registering runner: %v", err)
+	}
+
+	if _, err := fmt.Fprintf(ContextGetStdout(ctx), "%s", runner.UUID); err != nil {
 		panic(err)
 	}
 	return nil
diff --git a/routers/private/actions.go b/routers/private/actions.go
index 2403b9c41a..0c57bdacb7 100644
--- a/routers/private/actions.go
+++ b/routers/private/actions.go
@@ -4,6 +4,7 @@
 package private
 
 import (
+	gocontext "context"
 	"errors"
 	"fmt"
 	"net/http"
@@ -64,7 +65,11 @@ func GenerateActionsRunnerToken(ctx *context.PrivateContext) {
 	ctx.PlainText(http.StatusOK, token.Token)
 }
 
-func parseScope(ctx *context.PrivateContext, scope string) (ownerID, repoID int64, err error) {
+func ParseScope(ctx gocontext.Context, scope string) (ownerID, repoID int64, err error) {
+	return parseScope(ctx, scope)
+}
+
+func parseScope(ctx gocontext.Context, scope string) (ownerID, repoID int64, err error) {
 	ownerID = 0
 	repoID = 0
 	if scope == "" {
diff --git a/routers/private/forgejo.go b/routers/private/forgejo.go
deleted file mode 100644
index 97ae03468c..0000000000
--- a/routers/private/forgejo.go
+++ /dev/null
@@ -1,48 +0,0 @@
-// SPDX-License-Identifier: MIT
-
-package private
-
-import (
-	"fmt"
-	"net/http"
-
-	actions_model "code.gitea.io/gitea/models/actions"
-	"code.gitea.io/gitea/modules/context"
-	"code.gitea.io/gitea/modules/json"
-	"code.gitea.io/gitea/modules/log"
-	"code.gitea.io/gitea/modules/private"
-)
-
-func ActionsRunnerRegister(ctx *context.PrivateContext) {
-	var registerRequest private.ActionsRunnerRegisterRequest
-	rd := ctx.Req.Body
-	defer rd.Close()
-
-	if err := json.NewDecoder(rd).Decode(&registerRequest); err != nil {
-		log.Error("%v", err)
-		ctx.JSON(http.StatusInternalServerError, private.Response{
-			Err: err.Error(),
-		})
-		return
-	}
-
-	owner, repo, err := parseScope(ctx, registerRequest.Scope)
-	if err != nil {
-		log.Error("%v", err)
-		ctx.JSON(http.StatusInternalServerError, private.Response{
-			Err: err.Error(),
-		})
-	}
-
-	runner, err := actions_model.RegisterRunner(ctx, owner, repo, registerRequest.Token, registerRequest.Labels, registerRequest.Name, registerRequest.Version)
-	if err != nil {
-		err := fmt.Sprintf("error while registering runner: %v", err)
-		log.Error("%v", err)
-		ctx.JSON(http.StatusInternalServerError, private.Response{
-			Err: err,
-		})
-		return
-	}
-
-	ctx.PlainText(http.StatusOK, runner.UUID)
-}
diff --git a/routers/private/internal.go b/routers/private/internal.go
index ca0f942bad..407edebeed 100644
--- a/routers/private/internal.go
+++ b/routers/private/internal.go
@@ -56,7 +56,6 @@ func Routes() *web.Route {
 	// Since internal API will be sent only from Gitea sub commands and it's under control (checked by InternalToken), we can trust the headers.
 	r.Use(chi_middleware.RealIP)
 
-	r.Post("/actions/register", ActionsRunnerRegister)
 	r.Post("/ssh/authorized_keys", AuthorizedPublicKeyByContent)
 	r.Post("/ssh/{id}/update/{repoid}", UpdatePublicKeyInRepo)
 	r.Post("/ssh/log", bind(private.SSHLogOption{}), SSHLog)