Symptoms
On the Hobby plan, when deploying with the vercel CLI you see: Error: Unexpected error. Please try again later. ()
vercel --prod
🔍 Inspect: https://vercel.com/xx-projects/xx-site/xx [4s]
⏳ Preview: https://xx-site-xx-projects.vercel.app [4s]
Error: Unexpected error. Please try again later. ()With debug output (vercel --prod --debug), you see errors like:
[client-debug] 2026-03-16T06:25:05.466Z Waiting for builds and the deployment to complete...
> [debug] [2026-03-16T06:25:05.666Z] #4 ← 200 OK: sfo1::9z6ng-1773642305713-c49af82dc718 [201ms]
> [debug] [2026-03-16T06:25:05.815Z] #5 → GET https://api.vercel.com/v3/now/deployments/xxx
[client-debug] 2026-03-16T06:25:06.111Z Yielding a 'error' event
> [debug] [2026-03-16T06:25:06.113Z] Retrying: AbortError: The user aborted a request.
AbortError: The user aborted a request.
at abort2 (file:///xx/pnpm/global/5/.pnpm/vercel@50.32.5_typescript@5.9.3/node_modules/vercel/dist/chunks/chunk-QXRJ52T4.js:2796:23)
at AbortSignal.abortAndFinalize2 (file:///xx/pnpm/global/5/.pnpm/vercel@50.32.5_typescript@5.9.3/node_modules/vercel/dist/chunks/chunk-QXRJ52T4.js:2810:11)
at [nodejs.internal.kHybridDispatch] (node:internal/event_target:843:20)
at AbortSignal.dispatchEvent (node:internal/event_target:776:26)
at runAbort (node:internal/abort_controller:488:10)
at abortSignal (node:internal/abort_controller:459:3)
at AbortController.abort (node:internal/abort_controller:507:5)
at stopSpinner (file:///xx/pnpm/global/5/.pnpm/vercel@50.32.5_typescript@5.9.3/node_modules/vercel/dist/chunks/chunk-4S3Y3ATR.js:4321:22)
at processDeployment (file:///xx/pnpm/global/5/.pnpm/vercel@50.32.5_typescript@5.9.3/node_modules/vercel/dist/chunks/chunk-4S3Y3ATR.js:4474:9)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
> [debug] [2026-03-16T06:25:06.115Z] Error: ErrorThe CLI error is misleading; the real error is in the Vercel dashboard.
Go to:
👉 https://vercel.com → Deployments
You’ll see a deployment with status Blocked. Opening it shows:
### Deployment Blocked
Git author xxx@users.noreply.github.com must have access to the team xxx's projects on Vercel to create deployments.
---
Hobby teams do not support collaboration. Please upgrade to Pro to add team members.
Learn More Upgrade to Pro
Root cause
It’s not a CLI bug or a network issue—it’s deployment identity verification failing.
Typical setup:
- You have two or more Vercel Hobby accounts;
- Two Vercel projects are maintained with one GitHub account;
- Vercel Hobby only allows linking one GitHub account;
- So:
- The first project uses GitHub integration for deployment;
- The second uses the Vercel CLI to work around the one-account limit;
- This used to work, but recent policy changes broke it;
- Commits are made with one GitHub account, usually using
--globalGit config; - The
authorin the commit log only matches the first Vercel Hobby account; - Deploying the second project triggers the restriction.
The real issue: identity mismatch
Local machine
│
├─ Git
│ ├─ user.name
│ └─ user.email <- determines commit author
│
├─ SSH
│ ├─ private key
│ ├─ public key <- determines GitHub identity
│ └─ ~/.ssh/config
│
├─ GitHub
│ ├─ Account A
│ └─ Account B
│
└─ Vercel
├─ Account A / Team A
└─ Account B / Team B
└─ deployment permission check
In short: Git, SSH, GitHub, and Vercel are four separate identity systems that can conflict.
What Git / SSH / Vercel each do
SSH key: who you are for connecting to GitHubgit user.name / user.email: who is recorded as the author of each commit
git log -1
# output
commit d2d24af5187906626f81ae382305848b7b7e8e63
Author: your-name <xxx@users.noreply.github.com>
Date: Mon Mar 16 17:43:04 2026 +0800- GitHub account: who owns the repo and which SSH key is used
- Vercel: who can deploy, and whether the commit author belongs to the current team
Vercel verification logic
On the Hobby plan, Vercel checks:
- Which account/team the project belongs to;
- Who the current Git commit author is;
- Whether that commit author is allowed to deploy for that team.
If it finds:
- the commit author is not in this team, or
- it looks like “code committed by another user”,
it treats it as a collaboration scenario.
The Hobby plan does not support that for private repos, so the deployment is Blocked.
So the issue is not:
- SSH failing;
- Vercel CLI broken;
- GitHub push failing;
but:
Deployment identity verification failed.
In short: the Vercel CLI reads the current Git repo and checks:
git log -1
# output
commit d2d24af5187906626f81ae382305848b7b7e8e63 (HEAD -> main, origin/main)
Author: [vercel_account] <xxx@users.noreply.github.com>
Date: Mon Mar 16 17:43:04 2026 +0800That author is used for deployment identity verification.
If:
- this email is not the Vercel account email, or
- it belongs to another GitHub user, or
- it is not the team owner,
deployment is rejected:
- Vercel treats it as team collaboration (multiple users);
- Hobby does not support collaboration;
- For multiple users to deploy, you need to upgrade to Pro.
Vercel docs: Team configuration describes these limits in detail.
Hobby teams The Hobby Plan does not support collaboration for private repositories. If you need collaboration, upgrade to the Pro Plan.
To deploy commits under a Hobby team, the commit author must be the owner of the Hobby team containing the Vercel project connected to the Git repository. This is verified by comparing the Login Connections Hobby team's owner with the commit author.
To make sure we can verify your commits:
- Make sure all commits are authored by the git user associated with your account.
- Link your git provider to your Vercel account in Account Settings
If your account is not connected to your git provider, make sure you've properly configured your Vercel email address so that it matches the email associated with the commit.
For the most reliable experience, ensure both your project and account are properly connected to your git provider.
Quick fix (change Git author)
- In the repo, confirm the commit author:
git log -1- Check current Git config:
git config user.email- Set to the correct account:
git config user.name "Your Name"
git config user.email "your-vercel-email@example.com" Prefer repo-level config:
git config --local user.name "Your Name"
git config --local user.email "your-email@example.com"- Fix the latest commit:
git commit --amend --reset-author- Push and deploy again:
git push --force-with-lease
vercel --prodLong-term fix (multi-account isolation)
Typical setup:
Mac
├─ GitHub Account A
│ └─ SSH key A
│
├─ GitHub Account B
│ └─ SSH key B
│
├─ SSH config
│ ├─ github.com → Account A
│ └─ github-b → Account B
│
└─ Vercel
├─ Account A
└─ Account B
- One dev machine;
- Two or more GitHub accounts;
- Two or more SSH keys;
- Two or more Vercel accounts;
- SSH for GitHub;
- Vercel CLI for deployment.
Isolation principles
Goal: isolate by project so each project uses exactly one identity and you avoid the above errors.
- Each project is tied to one GitHub account;
- Each project is tied to one Vercel account/scope;
- Each project consistently uses that account’s:
git user.namegit user.email- SSH key;
- Avoid changing a project’s “owner” mid-way; if you do, migrate it fully once;
- Main-account repos: only main account’s email / main account’s noreply;
- Secondary-account repos: only that account’s email / that account’s noreply;
- Don’t mix noreply addresses across accounts;
- SSH alias must match the remote exactly.
Config
With multiple GitHub/Vercel accounts on one machine, avoid constantly changing --global.
Use:
- Global config for your primary account;
--localin each repo for projects that use a different account.
Steps
Generate separate private/public keys for each GitHub account;
- Open a terminal and generate a new SSH key:
ssh-keygen -t ed25519 -C "your_email@example.com"- When prompted:
- Use a different path so you don’t overwrite existing keys;
- You can leave the passphrase empty;
> Generating public/private ALGORITHM key pair.
> Enter a file in which to save the key (/Users/YOU/.ssh/id_ALGORITHM): [Press enter]
> Enter passphrase (empty for no passphrase): [Type a passphrase]
> Enter same passphrase again: [Type passphrase again]Rename the new key pair so you can tell them apart, e.g.:
id_ed25519_bid_ed25519_b.pub
- Add the public key to your GitHub account.
Configure ~/.ssh/config for main and secondary accounts;
Edit ~/.ssh/config like this:
Host github.com
# primary GitHub account
AddKeysToAgent yes
UseKeychain yes
IdentityFile ~/.ssh/id_ed25519_main
IdentitiesOnly yes
host github-b
# secondary GitHub account
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_b
IdentitiesOnly yes
Adjust repo setup
This describes migrating an existing project to a new GitHub account.
- Create a private repo under the new account;
- Copy the project into the new repo’s directory;
Initialize the repo:
git initSet this repo to use Account B’s identity:
git config user.name "Account B's GitHub username"
git config user.email "account-b-noreply@users.noreply.github.com"Verify the config:
git config --local --get user.name
git config --local --get user.email- Add the remote
Use the SSH alias github-b, not the default github.com.
git remote add origin git@github-b:[account]/[repo_name].gitCheck:
git remote -vFirst commit:
git add .
git commit -m "Initial commit"Set default branch to main:
git branch -M mainFirst push:
git push -u origin mainVerify deployment:
vercel --prodSSH basics
When you run:
git clone git@github.com:someone/repo.git
# or
git pushGit uses SSH to talk to GitHub. Your machine:
- Determines the host (e.g.
github.com) - Looks up
~/.ssh/configfor that host - Picks which private key to use
- GitHub checks the public key and matches it to an account
- If it matches, clone/push is allowed
So:
- Private key stays on your machine
- Public key is added to GitHub
- config decides which key to use for which host
SSH-related files live under ~/.ssh. Typical contents:
| File | Purpose |
|---|---|
| config | SSH client host rules and routing |
| id_ed25519 | Private key; keep secret |
| id_ed25519.pub | Public key; add to GitHub |
| known_hosts | Host keys of servers you’ve connected to |
| authorized_keys | Used on the server side |
~/.ssh/config
What to remember:
- It’s the SSH client’s “match host → use these options” file;
- It doesn’t store keys; it tells SSH which key and options to use for each target;
- Think of it as an alias/routing table.
It controls:
- Which host to connect to
- Which username
- Which private key
- Whether to add the key to the agent
- Whether to use macOS Keychain
- Other connection options
Main options:
| Option | Meaning |
|---|---|
Host | Start a block for a host (e.g. Host github.com → use this block when connecting to github.com) |
HostName | Real hostname when using an alias (e.g. Host github-a with HostName github.com → github-a connects to github.com) |
User | SSH login user. Examples: |
Service → SSH User: GitHub → git, GitLab → git, AWS EC2 → ec2-user, Ubuntu → ubuntu. So User git means ssh git@github.com | |
IdentitiesOnly | yes = only use the key from config, don’t try other keys |
IdentityFile | Path to the private key |
AddKeysToAgent | When this key is used, add it to ssh-agent (see ssh-agent) |
UseKeychain | On macOS, store the key’s passphrase in Keychain |
For example:
Host *
ServerAliveInterval 60
Host github.com
AddKeysToAgent yes
UseKeychain yes
IdentityFile ~/.ssh/id_ed25519_a
Host github-b
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_b
IdentitiesOnly yes
Host github.comandHost github-bare local aliases;- Both ultimately point to github.com but use different private keys.
So:
git clone git@github.com:accountA/repo.git
uses Account A’s key, and
git clone git@github-b:accountB/repo.git
uses Account B’s key. That’s the power of config and the basis for account isolation.
Host *
means “apply this to all SSH hosts”; here, send a keepalive every 60 seconds to avoid idle disconnects.
Host github.com
means “when connecting to github.com, use these options”:
AddKeysToAgent yes— add this key to ssh-agent so you don’t have to enter the passphrase repeatedly;UseKeychain yes— on macOS, store the passphrase in Keychain;IdentityFile ~/.ssh/id_ed25519— use this private key for GitHub; the matching public key must be added to your GitHub account for SSH auth.
ssh-agent
Without ssh-agent, SSH works like this:
- You run
git push - SSH connects to
github.com - SSH needs the private key
- If the key has a passphrase, you’re prompted:
Enter passphrase for key ~/.ssh/id_ed25519
- After you enter it, SSH can use the key.
ssh-agent is a local background process that caches unlocked private keys:
- First time: you enter the passphrase
- ssh-agent remembers the key
- Later, SSH gets signatures from the agent
- No more passphrase prompts
So: ssh-agent = local private-key cache.
~/.ssh/id_ed25519 and ~/.ssh/id_ed25519.pub
The generated private and public key pair.
~/.ssh/authorized_keys
Used when someone SSHs into this machine:
- The server checks
~/.ssh/authorized_keys - Only the public keys listed there are allowed to log in
So it answers: “which public keys are allowed to log in to this machine.” On your own Mac it’s used for:
- Remote login to your Mac
- Internal systems / jump hosts / Git servers
- Keys added by containers or automation
~/.ssh/known_hosts
- Protects against man-in-the-middle attacks
- Core SSH security
On first connection to a host (e.g. ssh root@xx), SSH receives the server’s host public key and may prompt:
The authenticity of host 'github.com' can't be established.
RSA key fingerprint is SHA256:xxxxx.
Are you sure you want to continue connecting (yes/no)?If you type yes, SSH stores the fingerprint in ~/.ssh/known_hosts, e.g.:
github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...
On later connections, SSH checks that the server’s host key matches what’s in known_hosts. If it doesn’t:
WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!That can mean the server was replaced, DNS was hijacked, or there’s a man-in-the-middle. SSH will refuse to connect.
If an attacker sits in the middle (you → attacker’s server → GitHub), they can’t present GitHub’s real host key, so the key won’t match known_hosts and SSH will refuse. This is SSH’s TOFU model:
Trust On First Use
Trust on first connection, verify consistency afterward
Git config and author
- Git config has layers;
- You can see which layer is in effect;
git authorand SSH identity are separate.
Git config
Git config mainly controls:
- Behavior — default branch, editor, line endings, etc.;
- Commit identity —
user.nameanduser.email.
git config user.name
git config user.emailThese determine the author in each Git commit. To see the latest commit:
git log -1
# example output:
commit d2d24af5187906626f81ae382305848b7b7e8e63
Author: your-name <xxx@users.noreply.github.com>
Date: Mon Mar 16 17:43:04 2026 +0800That Author is what Vercel uses for verification.
Git config has three layers:
| Layer | Command | Scope |
|---|---|---|
| system | git config --system | Whole system |
| global | git config --global | Current user |
| local | git config --local | Current repo |
You’ll mostly use --global and --local. Global applies to all repos for your user; local applies only to the current repo and overrides global.
View a value:
git config user.name
git config user.emailView global config:
git config --global --listView repo config:
git config --local --listView all config and where each value comes from:
git config --list --show-originUseful to see if a value comes from ~/.gitconfig, the repo’s .git/config, or system config.
Command reference
1. Git: current author
git config user.name
git config user.emailShows the author used for commits in this repo.
2. Git: global config
git config --global --listShows user-level Git config.
3. Git: repo config
git config --local --listShows this repo’s config.
4. Git: config origin
git config --list --show-originShows where each config value comes from.
5. Git: latest commit author
git log -1Shows the author of the latest commit; Vercel uses this for deployment checks.
6. Git: set repo author
git config --local user.name "Your Name"
git config --local user.email "your-email@example.com"Sets author only for this repo; other projects are unchanged.
7. Git: fix latest commit author
git commit --amend --reset-authorRewrites the latest commit’s author using current Git config.
8. Git: remote URLs
git remote -vShows fetch/push URLs; use to verify SSH alias.
9. Git: change remote URL
git remote set-url origin git@github-b:account/repo.gitUpdates the current repo’s remote.
10. SSH: list key files
ls -la ~/.sshLists SSH-related files.
11. SSH: keys in agent
ssh-add -lLists private keys currently loaded in ssh-agent.
12. SSH: test GitHub connection
ssh -T git@github.com
ssh -T git@github-bTests that each Host alias authenticates to the right GitHub account.
13. SSH: verbose connection
ssh -vT git@github-bShows which config and key SSH uses.
14. Vercel: current account
vercel whoamiShows which Vercel account the CLI is using.
15. Vercel: debug logs
vercel --prod --debugShows CLI debug output; for the actual error, check the dashboard Deployments page.