Cursor-agent does not inherit from direnv

Describe the Bug

Similar to this report it seems that environmental variables created by direnv are not part of the cursor-agent environment.

This is very problematic for me because I need to use a specific GitHub personal access token with one project. We check in .cursor/mcp.json to our Git repository, and have this config:

{
  "mcpServers": {
    "github": {
      "url": "https://api.githubcopilot.com/mcp/",
      "headers": {
        "Authorization": "Bearer ${env:GITHUB_PERSONAL_ACCESS_TOKEN}"
      }
    }
}

Since cursor is not correctly inheriting the PAT from .envrc, I cannot access GitHub MCP.

Steps to Reproduce

Set up direnv, create an .envrc with this content:

export DIRENV_ENABLED_FOR_CURSOR="yes"

Then start cusor-agent and run ! echo $DIRENV_ENABLED_FOR_CURSOR and there will be no output.

Expected Behavior

The cursor agent should inherit the shell environment.

Operating System

MacOS

Version Information

About Cursor CLI

CLI Version 2026.02.27-e7d2ef6
Model Claude 4.6 Sonnet
OS darwin (arm64)
Terminal wezterm
Shell zsh

Does this stop you from using Cursor

Sometimes - I can sometimes use Cursor

Hey, thanks for the detailed report. This is a known issue. The CLI starts a non-interactive shell (zsh -c), so it only sources ~/.zshenv, not ~/.zshrc. Since most users put eval "$(direnv hook zsh)" in .zshrc, direnv never runs.

Workaround: move the direnv hook to ~/.zshenv:

# ~/.zshenv
eval "$(direnv hook zsh)"

This makes sure direnv loads even in non-interactive shell sessions.

One more thing. The ${env:GITHUB_PERSONAL_ACCESS_TOKEN} syntax in your mcp.json is VS Code-specific and might not resolve in the CLI. If the workaround above doesn’t fix the MCP config, try ${GITHUB_PERSONAL_ACCESS_TOKEN} instead, without the env: prefix.

The team is aware of the CLI environment inheritance issue. Your report helps with visibility and prioritization.

Let me know if the workaround helps.

Removing the env: from ${env:GITHUB_PERSONAL_ACCESS_TOKEN} did allow GitHub MCP to authenticate, but it used my global GITHUB_PERSONAL_ACCESS_TOKEN instead of the one configured in .envrc, even after making the change to ~/.zshrenv. So no, the workaround suggested does not work.

Furthermore, removing env: from mcp.json is contradictory, because the cursor-agent mcp docs clearly state:

MCP in the CLI uses the same configuration as the editor

And if that is the case, then ENV interpolation should be using the env: prefix…? I don’t want to break my team’s ability to use Cusor (desktop) with this change.

Clearly there are some fundamental issues with cursor-agent and environment variables, and possibly some discrepancies between Cursor (desktop) and cusor-agent when it comes to MCP servers. This is very frustrating.

Hey, fair point. The .zshenv workaround doesn’t fully fix it. Even if the direnv hook loads, cursor-agent doesn’t guarantee that .envrc will activate for a specific project.

About the docs, you’re right. The CLI MCP page at MCP | Cursor Docs clearly says “MCP in the CLI uses the same configuration as the editor”, but in practice the ${env:...} syntax isn’t supported in the CLI.

So there are two issues:

  • cursor-agent doesn’t inherit direnv-specific environment variables
  • the ${env:...} syntax doesn’t resolve in the CLI, even though the docs claim parity with desktop

Both are on the team’s radar.

@deanrie is there a way to automatically inject environmental variables specifically into Cursor’s local agent at all–I don’t need to use direnv and would gladly use a workaround!?

I want to solve the same problem woody has mentioned but am not tied to using DIRENV

How can we fix this problem.

Hey, there isn’t a proper fix on the CLI side yet, but there is a workaround.

@elviskahoro if direnv isn’t a must, the simplest option is to export the needed variables in your session before starting cursor-agent:

export GITHUB_PERSONAL_ACCESS_TOKEN="your-token"
cursor-agent

Or do it in one go via a wrapper script or alias:

# ~/.zshenv or ~/.bashrc
export GITHUB_PERSONAL_ACCESS_TOKEN="your-token"

If you have lots of project-specific variables, you can run source .envrc && cursor-agent. This loads variables from .envrc into the current session before the agent starts.

For MCP config in the CLI, use ${GITHUB_PERSONAL_ACCESS_TOKEN} instead of ${env:GITHUB_PERSONAL_ACCESS_TOKEN}. The env: syntax isn’t supported in the CLI yet.

@acehinnnqru same workarounds above. The issue is that cursor-agent starts a non-interactive shell and doesn’t inherit variables from .zshrc or direnv. Until there’s a fix, you need to make sure the variables are available in the environment before starting the agent.

The team is aware of both issues inheritance and the ${env:...} syntax. There’s no ETA yet, but your reports help with prioritization.

Thanks for adding suggestions for us w.r.t CLI. I was primarily referring to the agent that’s automatically dispatched through the agent window. I’m looking to do something like this: https://github.com/elviskahoro/elviskahoro/blob/main/mcp.json

Now it makes sense what’s going on. The ${op://Personal/...} syntax is for 1Password CLI references via op run or op inject, and Cursor doesn’t resolve them. Cursor only supports ${env:VAR}, ${userHome}, ${workspaceFolder}, ${workspaceFolderBasename}, ${pathSeparator}. Everything else gets sent to the MCP server as a literal string.

Two working options:

Option 1: launch Cursor via op run

1Password CLI resolves op:// references into environment variables. Create a .env file with your secrets:

LINEAR_API_KEY=op://Personal/api-linear/credential
RESEND_API_KEY=op://Personal/api-resend/credential
DOSU_API_KEY=op://Personal/api-dosu/credential

Start Cursor like this:

op run --env-file="./.env" -- cursor .

In mcp.json, replace the op:// links with ${env:...}:

"env": {
  "LINEAR_API_KEY": "${env:LINEAR_API_KEY}"
}

And for the Dosu header:

"headers": {
  "X-Dosu-API-Key": "${env:DOSU_API_KEY}"
}

Option 2: pre-export via op read in your shell

In ~/.zshenv or a wrapper script:

export LINEAR_API_KEY=$(op read "op://Personal/api-linear/credential")
export RESEND_API_KEY=$(op read "op://Personal/api-resend/credential")
export DOSU_API_KEY=$(op read "op://Personal/api-dosu/credential")

Then run cursor . from the same shell. In mcp.json, use the same ${env:...}.

Important note for Option 2: if Cursor is launched from Dock or Finder on macOS, or via a desktop entry on Linux, it won’t see variables from shell rc files since those are only loaded by interactive shells. To work around that:

  • macOS: use launchctl setenv LINEAR_API_KEY "$(op read ...)" so the variable is available to GUI apps.
  • Linux: add variables to /etc/environment or ~/.pam_environment so they’re loaded at login regardless of shell.

Option 1 is cleaner since secrets only live in Cursor’s process memory during the session and don’t end up in the system-wide environment. Option 2 is more convenient if you always start Cursor the same way.

Hey @deanrie thanks again for the help! The main thing I’m trying to do is have any new agents made in Cursor’s desktop app through the agent window automatically load the environmental variables from a file that’s in that the directory that the agent boots up in.

Conductor handles this by letting us specify explicitly with an startup script for each agent session. In conductor I run this on every agent sessions:
```sh
PRIMARY_REPO_ROOT=“$(cd “$(dirname “$(git rev-parse --git-common-dir)”)” && pwd)”
[ ! -f .env.local ] && [ -f “$PRIMARY_REPO_ROOT/.env.local” ] && ln -s “$PRIMARY_REPO_ROOT/.env.local” .env.local
[ -f .env.local ] && set -a && source .env.local && set +a || true
[ ! -L .claude/skills ] && [ -d .agents/skills ] && mkdir -p .claude && ln -s ../.agents/skills .claude/skills
uv sync
```

here’s the conductor json:
{
“scripts”: {
“setup”: “PRIMARY_REPO_ROOT=”$(cd “$(dirname “$(git rev-parse --git-common-dir)”)” && pwd)“; [ ! -f .env.local ] && [ -f “$PRIMARY_REPO_ROOT/.env.local” ] && ln -s “$PRIMARY_REPO_ROOT/.env.local” .env.local; [ -f .env.local ] && set -a && source .env.local && set +a || true; [ ! -L .claude/skills ] && [ -d .agents/skills ] && mkdir -p .claude && ln -s ../.agents/skills .claude/skills; uv sync”,
“run”: “uv run elvis”
}
}

@elviskahoro for the desktop agent window there are two mechanisms that cover what Conductor is doing for you.

  1. sessionStart hook closest match to Conductor start script

Create .cursor/hooks.json in the repo:

{
  "version": 1,
  "hooks": {
    "sessionStart": [{ "command": ".cursor/hooks/load-env.sh" }]
  }
}

And the script .cursor/hooks/load-env.sh don’t forget chmod +x:

#!/usr/bin/env bash
set -euo pipefail

PRIMARY_REPO_ROOT="$(cd "$(dirname "$(git rev-parse --git-common-dir)")" && pwd)"
ENV_FILE=".env.local"

if [ ! -f "$ENV_FILE" ] && [ -f "$PRIMARY_REPO_ROOT/.env.local" ]; then
  ln -sf "$PRIMARY_REPO_ROOT/.env.local" "$ENV_FILE"
fi

# Collect env as JSON to return to the agent
if [ -f "$ENV_FILE" ]; then
  jq -Rn --arg file "$ENV_FILE" '
    {env: ([inputs | select(test("^\\s*[A-Za-z_][A-Za-z0-9_]*=")) | capture("^\\s*(?<k>[^=]+)=(?<v>.*)$") | {(.k): .v}] | add // {})}
  ' < "$ENV_FILE"
else
  echo '{}'
fi

The hook runs at the start of every agent session. The JSON with an env field injects variables into the environment for the shell tool. More details: Hooks | Cursor Docs

Caveat: variables from sessionStart.env don’t always get inherited by sub agents right now, and it also won’t re trigger when you come back to the chat after stopping a session. It’s on the radar, no ETA.

  1. If you work with worktrees use .cursor/worktrees.json

This is the closest match to your Conductor setup script. Cursor runs commands when creating a new worktree agent:

{
  "setup-worktree": [
    "[ -f $ROOT_WORKTREE_PATH/.env.local ] && ln -sf $ROOT_WORKTREE_PATH/.env.local .env.local",
    "[ -d .agents/skills ] && mkdir -p .claude && ln -sfn ../.agents/skills .claude/skills",
    "uv sync"
  ]
}

$ROOT_WORKTREE_PATH points to the primary worktree, so a symlink to .env.local from there works without copying secrets. Full reference: Worktrees | Cursor Docs

The worktree setup script by itself does not export variables into the agent environment because it runs in a separate process. For that, combine it with the sessionStart hook from 1, which will load .env.local.

Let me know how it goes.