diff --git a/.github/workflows/create-release-proposal.yml b/.github/workflows/create-release-proposal.yml
new file mode 100644
index 00000000000000..e7153019b5a72a
--- /dev/null
+++ b/.github/workflows/create-release-proposal.yml
@@ -0,0 +1,72 @@
+# This action requires the following secrets to be set on the repository:
+#   GH_USER_NAME: GitHub user whose Jenkins and GitHub token are defined below
+#   GH_USER_TOKEN: GitHub user token, to be used by ncu and to push changes
+#   JENKINS_TOKEN: Jenkins token, to be used to check CI status
+
+name: Create Release Proposal
+
+on:
+  workflow_dispatch:
+    inputs:
+      release-line:
+        required: true
+        type: number
+        default: 23
+        description: 'The release line (without dots or prefix). e.g: 22'
+      release-date:
+        required: true
+        type: string
+        default: YYYY-MM-DD
+        description: The release date in YYYY-MM-DD format
+
+concurrency: ${{ github.workflow }}
+
+env:
+  NODE_VERSION: lts/*
+
+permissions:
+  contents: read
+
+jobs:
+  releasePrepare:
+    env:
+      STAGING_BRANCH: v${{ inputs.release-line }}.x-staging
+      RELEASE_BRANCH: v${{ inputs.release-line }}.x
+      RELEASE_DATE: ${{ inputs.release-date }}
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332  # v4.1.7
+        with:
+          ref: ${{ env.STAGING_BRANCH }}
+          # Needs the whole git history for ncu to work
+          # See https://github.com/nodejs/node-core-utils/pull/486
+          fetch-depth: 0
+
+      # Install dependencies
+      - name: Install Node.js
+        uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b  # v4.0.3
+        with:
+          node-version: ${{ env.NODE_VERSION }}
+
+      - name: Install @node-core/utils
+        run: npm install -g @node-core/utils
+
+      - name: Configure @node-core/utils
+        run: |
+          ncu-config set branch "${RELEASE_BRANCH}"
+          ncu-config set upstream origin
+          ncu-config set username "$USERNAME"
+          ncu-config set token "$GH_TOKEN"
+          ncu-config set jenkins_token "$JENKINS_TOKEN"
+          ncu-config set repo "$(echo "$GITHUB_REPOSITORY" | cut -d/ -f2)"
+          ncu-config set owner "${GITHUB_REPOSITORY_OWNER}"
+        env:
+          USERNAME: ${{ secrets.JENKINS_USER }}
+          GH_TOKEN: ${{ secrets.GH_USER_TOKEN }}
+          JENKINS_TOKEN: ${{ secrets.JENKINS_TOKEN }}
+
+      - name: Start git node release prepare
+        run: |
+          ./tools/actions/create-release.sh "${RELEASE_DATE}" '${{ inputs.release-line  }}'
+        env:
+          GITHUB_TOKEN: ${{ secrets.GH_USER_TOKEN }}
diff --git a/tools/actions/create-release.sh b/tools/actions/create-release.sh
new file mode 100755
index 00000000000000..42e01731686736
--- /dev/null
+++ b/tools/actions/create-release.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+
+set -xe
+
+RELEASE_DATE=$1
+RELEASE_LINE=$2
+
+if [ -z "$RELEASE_DATE" ] || [ -z "$RELEASE_LINE" ]; then
+  echo "Usage: $0 <RELEASE_DATE> <RELEASE_LINE>"
+  exit 1
+fi
+
+git config --local user.email "github-bot@iojs.org"
+git config --local user.name "Node.js GitHub Bot"
+
+git node release --prepare --skipBranchDiff --yes --releaseDate "$RELEASE_DATE"
+# We use it to not specify the branch name as it changes based on
+# the commit list (semver-minor/semver-patch)
+git config push.default current
+
+TITLE=$(awk "/^## ${RELEASE_DATE}/ { print substr(\$0, 4) }" "doc/changelogs/CHANGELOG_V${RELEASE_LINE}.md")
+
+# Use a temporary file for the PR body
+TEMP_BODY=$(mktemp)
+awk "/## ${RELEASE_DATE}/,/^<a id=/{ if (!/^<a id=/) print }" "doc/changelogs/CHANGELOG_V${RELEASE_LINE}.md" > "$TEMP_BODY"
+
+gh pr create --title "$TITLE" --body-file "$TEMP_BODY"
+
+rm "$TEMP_BODY"
+# TODO: amend with proposal PR