0

I am using macOS and vanilla Emacs.

In my .zshrc file I have the following:

export OPENAI_API_KEY=my-key

The setting of the environment variable works as expected on the terminal (iTerm2):

➜  echo $OPENAI_API_KEY
my-key

In my emacs config file, if I hard code the key the package works as expected:

(use-package gptel
 :config
 (setq gptel-api-key "my-key")

However, I would like to avoid hardcoding my key in my init file. I would like to make Emacs read from the environment variables.

If I try reading from the environment variable, it does not work. I tried the two approaches below:

(use-package gptel
 :config
 (setq gptel-api-key (shell-command-to-string "$SHELL --login -c 'echo $OPENAI_API_KEY'")))

And also:

(use-package gptel
 :config
 (setq gptel-api-key (getenv "OPENAI_API_KEY")))

It does not work. The system behaves as if the key was not provided.

gptel documentation suggests using ~/.authinfo.

Isn't there an easier way than authinfo?

Why don't any of my approaches work? Can these approaches be fixed?

2
  • 1
    What is the output from (shell-command-to-string "$SHELL --login -c 'echo $OPENAI_API_KEY'")? Did you try it?
    – shynur
    Jul 10, 2023 at 1:49
  • After executing the command eval-expression, this is what I got: " " Jul 10, 2023 at 2:24

1 Answer 1

5

Because you don’t understand environment variables (you’re not alone here, this QA forum is littered with duplicate questions from people who don’t understand them). Environment variables are inherited from the parent process. What this means is that when one application starts another, it passes along it’s own environment to the newly started application.

When zsh starts up, it inherits some environment variables from it's own parent process. It then reads and executes your startup files, including ~/.zshrc, which will probably set some environment variables to new values. If you then type emacs into that shell and hit enter, zsh will pass those values along to Emacs, and getenv will do what you want.

But if you start Emacs from a button on the OSX dock instead of from inside zsh, then Emacs will not inherit its environment variables from zsh. It will inherit them from the OS, the same as zsh would have. Emacs will read it’s own startup files, not zsh’s startup files, and will set whatever variables are set by them.

Launching a shell and asking it for the value of the variable is the right approach here (although putting the information into your ~/.authinfo file is also a great idea). You can simplify the code that you included in your question:

(use-package gptel
 :config
 (setq gptel-api-key (shell-command-to-string "echo $OPENAI_API_KEY")))

shell-command-to-string already runs the command in a shell, so you shouldn’t have to run another shell inside of that. The only exception is if zsh only runs ~/.zshrc if it is a login shell, in which case specifying the --login option was one way to get it to work. The other way to get it to work is to find the init file that zsh will run in all shells, whether they are a login shell or not. The zsh man page (which you can read with M-x man), says this:

STARTUP/SHUTDOWN FILES

       Commands are first read from /etc/zshenv; this cannot be
       overridden.  Subsequent behaviour is modified by the RCS and
       GLOBAL_RCS options; the former affects all startup files, while
       the second only affects global startup files (those shown here
       with an path starting with a /).  If one of the options is
       unset at any point, any subsequent startup file(s) of the
       corresponding type will not be read.  It is also possible for a
       file in $ZDOTDIR to re-enable GLOBAL_RCS. Both RCS and
       GLOBAL_RCS are set by default.

       Commands are then read from $ZDOTDIR/.zshenv.  If the shell is
       a login shell, commands are read from /etc/zprofile and then
       $ZDOTDIR/.zprofile.  Then, if the shell is interactive,
       commands are read from /etc/zshrc and then $ZDOTDIR/.zshrc.
       Finally, if the shell is a login shell, /etc/zlogin and
       $ZDOTDIR/.zlogin are read.

Since Emacs is starting a non–interactive, non–login shell, you should set the OPENAI_API_KEY variable in your ~/.zshenv file instead of your ~/.zshrc file. This file is read first no matter what mode zsh is launched in, so it will take effect for your normal shells as well.

6
  • 1
    Good answer. I didn't know how env-vars are inherited under GUI before.
    – shynur
    Jul 10, 2023 at 5:45
  • 1
    Doesn't macOS provide a place where environment variables can be defined so that any program in a user session will inherit them? Pre-GUI, login shells did that with ~/.profile or equivalent and today Linux distros using systemd do a similar thing with ~/.config/environment.d/00-profile.conf or equivalent. The situation in-between was far less well defined: I guess macOS is still in that in-between state?
    – NickD
    Jul 10, 2023 at 15:13
  • 1
    FYI, there is either an incomplete sentence or perhaps stuff you forgot to delete at the end of the first line in your second paragraph: ` (whic is.`
    – NickD
    Jul 10, 2023 at 15:24
  • 1
    fixed it, thanks :)
    – db48x
    Jul 10, 2023 at 15:38
  • 1
    Oh, and I have no idea if OSX does anything special with environment variables. I know that Windows has for a long time now allowed you to edit the environment that programs started via the shell will have. Maybe OSX has something similar.
    – db48x
    Jul 10, 2023 at 15:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.