Cursor agent - Signed commits

Hey :waving_hand: - I’ve been using the Cursor agent every single day since it was released, and loving it! The only issue I’ve found so far is contributing to open-source projects that require signed commits due to legal reasons.

Usually, big OSS projects require all commits to be signed appropriately, and in our first contribution, further automation asks contributors to sign digitally an authorization to use the code they wrote (see exporter/prometheus: Promote `EnableNativeHistograms` feature flag to beta. by ArthurSens · Pull Request #41105 · open-telemetry/opentelemetry-collector-contrib · GitHub as an example).

While using the agent, the cursor does not sign commits by default, and I’m having trouble making this work even after I tell it to. When I tell cursor to sign all commits with my email, it runs git config-- global user.email "my email" and then hangs forever!

Has anyone faced this before? Any workaround that worked?

15 Likes

Hey @ArthurSens have you resolved this issue? Did Cursor team address it?

Nop, not really… I can just copy the code manually and commit stuff myself, but I was hoping for something less manual

1 Like

Yeah, I had to resign the commits myself.

1 Like

+1!

1 Like

BTW our pipeline requires signed commits. It’s a significant slowdown to the team wanting to use cursor agents having to go in and force push signed commits for each branch before merging. Would be great if we could somehow have agents sign their commits and whitelist the agent as a contributor.

2 Likes

:+1: +1 to this

1 Like

:plus: to this

1 Like

:plus: to this

:plus: :folded_hands:

+1 :folded_hands:

Edit: This explains how to configure GPG in the local environment because I misinterpreted the post. See my next answer for how to set this up in the cloud environment. You’ll still need to generate a GPG key for that, but you won’t have to do all this manual setup.


First let me clarify something real quick: Your name and email are attached to a commit automatically as long as you have configured them globally in git (more on that later in this answer). The signing is actually done using an asymmetric key pair, usually through GPG. Your name and email are also associated with the GPG key (and are automatically attached alongside the signature), so a signed commit technically contains that information twice - once from git, and once from GPG. Keep that in mind when configuring the two; ideally use the same name and email for both to eliminate any potential confusion. I won’t get into that too much, but what you want to accomplish is this: When you make a commit, you want git to invoke GPG to make a cryptographic signature on the commit, so that someone else can check the signature later using a “public key” you give them.

I’m going to assume you’re talking about signing commits with GPG here, since it’s the most common method. I’ll also assume you already have a GPG key pair for your name and email address, and that you have already added the public key to your GitHub account. If you don’t, download GPG using one of the following methods:

  • Linux: GPG is pre-installed already on most distros (probably all the common ones). If you don’t have it for some reason (i.e. the gpg command is missing), install it using your system’s package manager.
  • MacOS: Install it using homebrew.
  • Windows: Download it from gpg4win. That is the official windows download - the GPG homepage links to it.

Once you have gpg installed, follow the great GitHub guides on generating a new GPG key pair and adding the public key to your GitHub account. (They also have a short doc on signing commits if you want a starting point for some additional reading). Adding the public key to your GitHub account is an important step. If you don’t do that, GitHub won’t be able to verify that your commits are properly signed. Quick note for those guides: Make sure you use the same email address as your GitHub account when generating the keys. If you don’t, you’ll probably encounter an issue where GitHub fails to verify your commits because it looks for a GPG key under a different email (the one your signing key specified).

Apparently you can also sign commits using an SSH or S/MIME key as well. However, I’ve never seen anyone do that nor had to do it myself in practice, so I’d stick with GPG unless specifically instructed otherwise.

Also, make sure you (globally) configure Git to use your name and email address for commits:

git config --global user.name 'Yourfirstname Yourlastname'
git config --global user.email '[email protected]'

At this point, verify that your configuration is correct. In the terminal, run:

git config --global user.name
# ^ should output your full name

git config --global user.email
# ^ should output your email address

gpg -k
# ^ should output something like:
#
# pub   ed25519 2025-07-15 [C] [expires: 2027-07-15]
#     ABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890101
# uid           [ultimate] Yourfirstname Yourlastname <[email protected]>
# sub   cv25519 2025-07-15 [E] [expires: 2027-07-15]
# sub   ed25519 2025-07-15 [S] [expires: 2027-07-15]

Take note of the string of random characters like ABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890101. That’s your GPG key ID. You’ll need it in the next step.

Now that we have a key pair and the basic git info in place, we’ll configure git to sign commits by default. In the terminal, run:

# This is where you should use your GPG key ID from the previous step.
# Don't use this one - it's just a placeholder.
git config --global user.signingKey 'ABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890101'

# Configure git to sign commits by default
git config --global commit.gpgsign true

As an added bonus, you can also have git sign tags by default:

git config --global tag.gpgSign true

You can verify that all this has taken effect by viewing your ~/.gitconfig file ($env:USERPROFILE\.gitconfig if accessed from PowerShell on Windows). It should look something like this:

[user]
	name = Yourfirstname Yourlastname
	email = [email protected]
	signingKey = ABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890101
[commit]
	gpgsign = true
[tag]
	gpgSign = true

Now try making a commit the same way you always do, either through the terminal or through Cursor’s UI. GPG may ask you to input a password if you assigned one to your key. Once the commit succeeds, verify that the signature actually happened by running

git log --show-signature

in the repository. If it succeeded, your output should look something like this:

commit 9a3fca344fc4fa18dd23b6762deed51254f4ad80 (HEAD -> main)
gpg: Signature made Fri Nov  7 04:27:14 2025 CST
gpg:                using EDDSA key ABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890101
gpg: Good signature from "Yourfirstname Yourlastname <[email protected]>" [ultimate]
Author: Yourfirstname Yourlastname <[email protected]>
Date:   Fri Nov 7 04:27:14 2025 -0600

where a successful signature and verification is denoted by the Good signature from "Yourfirstname ... message.

If your commit/signature failed

While debugging the signing process, you can use echo 'test' | gpg --clearsign to see whether GPG actually runs properly. If your GPG is set up and working properly, you’ll get an output like this:

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512

test
-----BEGIN PGP SIGNATURE-----

iHUEARYKAB0WIQRI3DqE2xJF0MxyuA+WQUVgYGHLGQUCaQ3g/QAKCRCWQUVgYGHL
GfW2AQCyiLYY3vc7/zH+kE0whMLYvterztOhJl/+MsCIJ2pLVwEA0vjZkuVXgbyw
MURbaOiJ59dPXvj0z5GyHP5lr7NX0QE=
=UrJF
-----END PGP SIGNATURE-----

And if you think maybe git isn’t invoking gpg automatically, you can manually tell it to sign a commit using the -S flag:

git commit -S -m 'Awesome commit message'

Failed On MacOS

Signing commits will fail by default on Macbooks and MacOS because it doesn’t have a pinentry program installed by default. When Git invokes GPG to sign a commit, GPG has no way to prompt the user for a password, and fails with this message:

error: gpg failed to sign the data fatal: failed to write commit object

Follow these steps to resolve:

  1. brew install pinentry-mac
  2. Add this line to ~/.gnupg/gpg-agent.conf :
pinentry-program /opt/homebrew/bin/pinentry-mac
  1. Kill GPG agent: killall gpg-agent, then try the commit/signature again. It should work.

Failed On Windows

The gpg4win installation (official GPG distribution for Windows) is finicky because it can conflict a bit with the GPG that comes preinstalled with “Git Bash”, the bash shell that ships with the Git Windows installation for some reason. For me, the issue is usually that the GPG agent doesn’t launch properly on startup. I don’t know exactly why that is, but you can fix it by running these commands:

gpgconf --launch gpg-agent
gpg-connect-agent.exe /bye
gpgconf --reload gpg-agent

and if that still doesn’t work, just launch the program called Kleopatra. It’s part of the gpg4win distribution. Once Kleopatra finishes loading, your commit should work.

Trying these two things should solve the issue 100% of the time.

Failed On Linux through SSH

Add this to your .bashrc, .zshrc, or whatever config file you use for your shell:

export GPG_TTY=$(tty)

Then either restart the SSH session (log out then log back in), or reload the shell with a command like exec bash, exec zsh, etc. (whichever shell you use). Once you’re back in the reloaded session, install pinentry-curses using your distro’s package manager, and add this line to ~/.gnupg/gpg-agent.conf:

pinentry-program /usr/bin/pinentry-curses

That gives you a nice full-terminal password entry experience. You can probably use pinentry-tty instead if you want - I just like pinentry-curses better.

Failed on Linux Desktop environment

I have no idea how to solve this one, sorry. Never seen gpg fail on Linux outside an SSH session.

I meant the background agents

@ArthurSens The cloud agents are working for me again, so I was finally able to test this. Here’s a working system you can use to have cursor cloud agents sign every commit (at least, when launched from the editor. I don’t think this will work if the agents are launched from Slack or GitHub).

The idea is:

  1. Put needed user-specific information (email, GPG private key, etc.) in cloud agent user secrets.
  2. Write a bash script to handle the GPG configuration I described above.
  3. Configure .cursor/environment.json to run the script during the cloud agent install step (initialization).

This solution is set up so that the shell scripts are downloaded on the fly, so they don’t have to be saved in a repository (that way you can use this when contributing to other projects). I ended up splitting this into two scripts just so I had a simpler mental model of the variable scopes. It can probably be condensed into one script, but this works. You can copy these exactly as they are without change - they’re configured using cursor secrets (environment variables in the cloud env).


setup.sh: This runs the init-gpg.sh script then unsets sensitive environment variables so they aren't accessible in the project's actual install/init command.
#!/bin/bash
# This script is designed to be SOURCED in Cursor Cloud Agent environments
# Usage: source <(curl -fsSL "$SCRIPT_DOWNLOAD_ROOT_URL/setup.sh")

set -euo pipefail

# Validate that the repository URL is set
if [[ -z "${SCRIPT_DOWNLOAD_ROOT_URL:-}" ]]; then
    echo "ERROR: SCRIPT_DOWNLOAD_ROOT_URL environment variable not set" >&2
    echo "Please add it as a secret in Cursor Dashboard" >&2
    echo "Example: https://raw.githubusercontent.com/YOUR_USERNAME/your-repo/main" >&2
    return 1
fi

echo "[Setup] Using repository: $SCRIPT_DOWNLOAD_ROOT_URL"
echo "[Setup] Downloading GPG init script from: $SCRIPT_DOWNLOAD_ROOT_URL/init-gpg.sh"

# Download and execute init-gpg.sh directly without saving to disk
if ! curl -fsSL "$SCRIPT_DOWNLOAD_ROOT_URL/init-gpg.sh" | bash; then
    echo "ERROR: GPG initialization failed" >&2
    return 1
fi

# Security: Clear sensitive environment variables to prevent exposure to subsequent
# commands in the Cloud agent "install" step (e.g., npm install). This protects
# against malicious dependencies.
echo "[Setup] Clearing sensitive environment variables..."
unset SCRIPT_DOWNLOAD_ROOT_URL
unset GPG_PRIVATE_KEY_BASE64
unset GPG_PRIVATE_KEY_PASSPHRASE
unset MY_GIT_EMAIL
unset MY_FULL_NAME

echo "[Setup] GPG configuration complete"

init-gpg.sh: This does the full GPG setup I described earlier, but for the headless environment. It configures the cloud agent environment to automatically sign every commit with your GPG private key without requiring any human intervention.
#!/bin/bash
set -euo pipefail

# Safety check: Only run in Cursor Cloud Agent environments
# Set IS_RUNNING_CURSOR_CLOUD_AGENT=1 as a secret in Cursor Dashboard
if [[ -z "${IS_RUNNING_CURSOR_CLOUD_AGENT:-}" ]]; then
    echo "ERROR: This script is designed for Cursor Cloud Agents only." >&2
    echo "Skipping GPG setup to avoid breaking your local configuration." >&2
    echo "If you are seeing this error message within a Cursor Cloud Agent environment, please set IS_RUNNING_CURSOR_CLOUD_AGENT=1 in the Cursor dashboard. It should work afterwards." >&2
    exit 1
fi

# Required environment variables (from Cursor Secrets)
: "${GPG_PRIVATE_KEY_BASE64:?Error: GPG_PRIVATE_KEY_BASE64 not set in Cursor Secrets}"
: "${GPG_PRIVATE_KEY_PASSPHRASE:?Error: GPG_PRIVATE_KEY_PASSPHRASE not set in Cursor Secrets}"
: "${MY_GIT_EMAIL:?Error: MY_GIT_EMAIL not set in Cursor Secrets}"
: "${MY_FULL_NAME:?Error: MY_FULL_NAME not set in Cursor Secrets}"

echo "Setting up GPG signing for $MY_FULL_NAME..."

# Initialize GPG home with proper permissions
export GNUPGHOME="${GNUPGHOME:-$HOME/.gnupg}"
mkdir -p "$GNUPGHOME"
chmod 700 "$GNUPGHOME"

# Configure gpg-agent for non-interactive operation. Allowing a preset passphrase allows
# us to enter the password during cloud agent initialization, so it doesn't have to be entered
# after each commit.
cat > "$GNUPGHOME/gpg-agent.conf" <<EOF
allow-preset-passphrase
default-cache-ttl 28800
max-cache-ttl 86400
EOF

# Ensure the above configuration is applied.
gpgconf --kill gpg-agent 2>/dev/null || true
gpgconf --launch gpg-agent

# Decode and import private key (base64 -> ASCII-armored GPG key)
echo "$GPG_PRIVATE_KEY_BASE64" | base64 -d | gpg --batch --import 2>/dev/null

KEY_INFO=$(gpg --with-colons --with-keygrip --list-secret-keys "$MY_GIT_EMAIL" 2>/dev/null)
KEYGRIPS=$(echo "$KEY_INFO" | awk -F: '/^grp:/ {print $10}')
FINGERPRINT=$(echo "$KEY_INFO" | awk -F: '/^fpr:/ {print $10; exit}')

if [[ -z "$KEYGRIPS" ]] || [[ -z "$FINGERPRINT" ]]; then
    echo "Error: Failed to extract keygrip(s) or fingerprint for $MY_GIT_EMAIL" >&2
    echo "Available keys:" >&2
    gpg --list-secret-keys >&2
    exit 1
fi

# Find and use gpg-preset-passphrase (Ubuntu typically has it in /usr/lib/gnupg).
# This is what allows us to preload the passphrase, to ensure the agent doesn't require any
# user interaction for signing.
GPG_PRESET=""
for preset_path in \
    "/usr/lib/gnupg/gpg-preset-passphrase" \
    "/usr/lib/gnupg2/gpg-preset-passphrase" \
    "/usr/libexec/gpg-preset-passphrase" \
    "$(command -v gpg-preset-passphrase 2>/dev/null || echo '')"; do
    if [[ -x "$preset_path" ]]; then
        GPG_PRESET="$preset_path"
        break
    fi
done

if [[ -z "$GPG_PRESET" ]]; then
    echo "Error: gpg-preset-passphrase not found. Installing gnupg2..." >&2
    # gpg-preset-passphrase is only supported in GPG 2.0 or later. I think Ubuntu will have this
    # by default, but if they don't, we'll install it.
    sudo apt-get update && sudo apt-get install -y gnupg2
    GPG_PRESET="/usr/lib/gnupg/gpg-preset-passphrase"
fi

# Load the passphrase into gpg-agent cache for all keygrips (primary + subkeys).
# This ensures gpg will never ask for the passphrase, regardless of which key it uses.
# As a result, also ensures it works with non-default GPG key setups like using
# a subkey for signing instead of the primary key.
for KEYGRIP in $KEYGRIPS; do
    printf '%s' "$GPG_PRIVATE_KEY_PASSPHRASE" | "$GPG_PRESET" --preset "$KEYGRIP"
done

# Configure Git globally for automatic signing
git config --global user.name "$MY_FULL_NAME"
git config --global user.email "$MY_GIT_EMAIL"
git config --global user.signingkey "$FINGERPRINT"
git config --global commit.gpgsign true
git config --global gpg.program "gpg"

# Quick double check to make sure we can actually sign things without additional interaction.
if echo "test" | gpg --batch --yes \
    --local-user "$FINGERPRINT" --clearsign >/dev/null 2>&1; then
    echo "[OK] GPG signing configured successfully"
    echo "  Name: $MY_FULL_NAME"
    echo "  Email: $MY_GIT_EMAIL"
    echo "  Fingerprint: $FINGERPRINT"
else
    echo "ERROR: GPG signing verification failed!" >&2
    echo "Debugging information:" >&2
    gpg --list-secret-keys "$MY_GIT_EMAIL" >&2
    echo "" >&2
    echo "Attempting test signature with verbose output:" >&2
    echo "test" | gpg --batch --yes \
        --local-user "$FINGERPRINT" --clearsign 2>&2 || true
    exit 1
fi

Example .cursor/environment.json
{
  "install": "{ [[ -n \"${SCRIPT_DOWNLOAD_ROOT_URL:-}\" ]] || { echo \"ERROR: SCRIPT_DOWNLOAD_ROOT_URL not set in Cursor Secrets\" >&2; false; }; } && { echo \"[Setup] Downloading from: $SCRIPT_DOWNLOAD_ROOT_URL/setup.sh\" && _setup_script=$(curl -fsSL \"$SCRIPT_DOWNLOAD_ROOT_URL/setup.sh\") && source /dev/stdin <<< \"$_setup_script\"; } && npm install",
  "terminals": [
    {
      "name": "Dev Server",
      "command": "npm run dev"
    }
  ]
}

  • setup.sh and init-gpg.sh have to be put in their own separate repository, or somewhere on the web where they can be freely downloaded from the same base URL.

  • .cursor/environment.json has to be put in the repository you’re working on. I imagine many open source projects wouldn’t appreciate you adding this to their repo, so I recommend adding it to your local, private ignore file instead of .gitignore: echo ".cursor/environment.json" >> .git/info/exclude

Once those are all ready, configure these user secrets in cursor’s cloud agent dashboard (or the editor settings):

  • IS_RUNNING_CURSOR_CLOUD_AGENT: Flag to safeguard against running this script in a local environment. Set it to 1 in secrets.
  • SCRIPT_DOWNLOAD_ROOT_URL: Base URL for downloading setup scripts. For example, if your scripts repository is hosted on github and called my-scripts-repo and the scripts you want are available on the main branch, your root URL will be https://raw.githubusercontent.com/YOUR_GITHUB_USERNAME/my-scripts-repo/main
  • GPG_PRIVATE_KEY_BASE64: base64 encoded ASCII-armored GPG private key.
  • GPG_PRIVATE_KEY_PASSPHRASE: Password for your private key.
  • MY_GIT_EMAIL: Email address associated with your GPG key and Git commits.
  • MY_FULL_NAME: Your full name (also associated with commits and GPG key).

The private key is base64 encoded just so I can guarantee nothing breaks when I put it in the single-line secret input. Use this to export the key in that way:

# List your GPG keys to find the key ID
gpg --list-secret-keys --keyid-format=long

# Export the private key (replace YOUR_KEY_ID with your actual key ID)
# The 'tr -d' command removes newlines to create a single-line string
gpg --armor --export-secret-keys YOUR_KEY_ID | base64 | tr -d '\n'; echo

And a quick note about the cloud agent environment file, the && npm install is the actual init command for the project. Everything before that is part of the setup to call setup.sh properly. Feel free to change that and the terminal list as needed to fit your project.


To summarize, here’s the expected setup:

  1. You have the two shell scripts saved in an external repository or hosted on a server so that you can download them with an HTTP request (hosting in a github repo is fine). Make sure the names are exactly setup.sh and init-gpg.sh.
  2. You have a .cursor/environment.json file in your locally cloned copy of the project, ensured git ignores it by specifying it in your private, local .git/info/exclude file.
  3. All 6 user SECRETS are properly configured.

Auto-signing for all commits should work once you have all that in place.