wavetermdev/waveterm · error

invalid environment variable name: %q

Error message

invalid environment variable name: %q

What it means

BuildShellCommand renders a CommandSpec into a single 'sh -c' command string, inlining environment variables as KEY=value assignments. Before inlining, each env var key is validated with isValidEnvVarName (regex ^[a-zA-Z_][a-zA-Z0-9_]*$); this error is thrown for any key that does not match, because such a key would produce a broken or injection-prone shell assignment. The command is not built or run.

Source

Thrown at pkg/genconn/genconn.go:142

		return nil, fmt.Errorf("failed to get stdout pipe: %w", err)
	}
	return syncbuf.MakeSyncBufferFromReader(stdout), nil
}

func MakeStderrSyncBuffer(proc ShellProcessController) (*syncbuf.SyncBuffer, error) {
	stderr, err := proc.StderrPipe()
	if err != nil {
		return nil, fmt.Errorf("failed to get stderr pipe: %w", err)
	}
	return syncbuf.MakeSyncBufferFromReader(stderr), nil
}

func BuildShellCommand(opts CommandSpec) (string, error) {
	// Build environment variables
	var envVars strings.Builder
	for key, value := range opts.Env {
		if !isValidEnvVarName(key) {
			return "", fmt.Errorf("invalid environment variable name: %q", key)
		}
		envVars.WriteString(fmt.Sprintf("%s=%s ", key, shellutil.HardQuote(value)))
	}

	// Build the command
	shellCmd := opts.Cmd
	if opts.Cwd != "" {
		shellCmd = fmt.Sprintf("cd %s && %s", shellutil.HardQuote(opts.Cwd), shellCmd)
	}

	// Quote the command for `sh -c`
	return fmt.Sprintf("sh -c %s", shellutil.HardQuote(envVars.String()+shellCmd)), nil
}

func isValidEnvVarName(name string) bool {
	validEnvVarName := regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*$`)
	return validEnvVarName.MatchString(name)
}

View on GitHub (pinned to a4447c1563)

Solutions

  1. Sanitize CommandSpec.Env keys: keep only those matching ^[a-zA-Z_][a-zA-Z0-9_]*$ and drop or rename the rest before calling.
  2. If you have raw 'K=V' strings, split them into map entries instead of using 'K=V' as the key.
  3. For keys like 'ProgramFiles(x86)' that legitimately cannot be passed this way, drop them or export them inside Cmd itself (e.g. via export statements in the command string).
  4. Log the offending key (it is quoted with %q) and fix it at the source of the env map construction.
  5. Add a pre-flight validation of the env map in caller code to fail fast with a clear message.

Example fix

// before
env := map[string]string{"MY-VAR": "x", "1BAD": "y"}
_, err := genconn.BuildShellCommand(genconn.CommandSpec{Cmd: "run", Env: env}) // errors
// after
env := map[string]string{"MY-VAR": "x", "1BAD": "y"}
safeEnv := make(map[string]string)
for k, v := range env {
    if regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*$`).MatchString(k) {
        safeEnv[k] = v
    } else {
        log.Printf("dropping invalid env key %q", k)
    }
}
cmdStr, err := genconn.BuildShellCommand(genconn.CommandSpec{Cmd: "run", Env: safeEnv})
Defensive patterns

Strategy: validation

Validate before calling

var validEnvKey = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*$`)

func sanitizeEnv(env map[string]string) map[string]string {
    safe := make(map[string]string, len(env))
    for k, v := range env {
        if validEnvKey.MatchString(k) {
            safe[k] = v
        }
    }
    return safe
}
// usage: BuildShellCommand(CommandSpec{Cmd: cmd, Env: sanitizeEnv(env)})

Type guard

func isValidEnvKey(k string) bool {
    for i, c := range k {
        if !(c == '_' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
            (i > 0 && c >= '0' && c <= '9')) {
            return false
        }
    }
    return len(k) > 0
}

Try / catch

cmdStr, err := genconn.BuildShellCommand(spec)
if err != nil {
    var badKey string
    if fmt.Sscanf(err.Error(), "invalid environment variable name: %q", &badKey) == 1 {
        return fmt.Errorf("fix env key %q in caller config (allowed: [A-Za-z_][A-Za-z0-9_]*)", badKey)
    }
    return err
}

Prevention

When it happens

Trigger: CommandSpec.Env contains a key that is empty, contains characters outside [a-zA-Z0-9_], starts with a digit, or contains '=' (already an assignment) — e.g. env maps built from raw strings like "FOO=bar" instead of key/value pairs.

Common situations: Passing OS environment strings (os.Environ() output split incorrectly), Windows-style or WSL env values with odd characters in the key, config-driven env vars with typos like 'MY-VAR' or '1PATH', or copying whole env blocks including keys like 'ProgramFiles(x86)'.

Related errors


AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01). Data as JSON: /api/errors/363a154ed2bf942b. Report an issue: GitHub.