PTY & Shell

SHELL_SESSIONS_DISABLE and Other macOS Shell Environment Setup

When PTYSession forks a child process, it sets four environment variables before calling execvp. Each has a specific purpose in the macOS shell environment.

The Four Variables

// mac_agent/Sources/MacAgentLib/PTYSession.swift
if pid == 0 {
    // Child process
    setenv("TERM", "xterm-256color", 1)
    setenv("LANG", "en_US.UTF-8", 1)
    setenv("SHELL_SESSIONS_DISABLE", "1", 1)
    if let sid = sessionId { setenv("TERMONMAC_SESSION", sid, 1) }

TERM=xterm-256color

TERM declares the terminal type to the shell and to programs using the terminfo database. With xterm-256color:

  • Programs using ncurses (vim, tmux, htop) query terminfo for escape sequences
  • Color output works correctly (256-color palette)
  • Cursor movement, line clearing, and other terminal capabilities are available

Without TERM, many programs default to “dumb” terminal mode and emit no control sequences, producing flat, uncolored output.

LANG=en_US.UTF-8

LANG controls locale settings for character encoding, number formatting, and string collation. UTF-8 is required for correct handling of multi-byte characters in terminal output.

When LANG is unset or set to a non-UTF-8 locale, multi-byte characters (emoji, CJK characters, accented characters) may be split incorrectly or displayed as garbage.

SHELL_SESSIONS_DISABLE=1

This is a macOS-specific variable. Since macOS Sierra, zsh and bash save per-session history and state to ~/.zsh_sessions or ~/.bash_sessions:

  • ~/.zsh_sessions/{UUID}.history — session-specific command history
  • ~/.zsh_sessions/{UUID}.session — session state (working directory, etc.)

The purpose is to let Terminal.app restore session history when a window is reopened. For a programmatic PTY spawned by a daemon this backfires in a concrete, user-visible way: the shell prints Restored session: ... on startup, and because the session files are written with rw------- permissions, the shell also emits override rw------- confirmation prompts. Both of these show up as noise in the first screen the iOS app renders — not an abstract cleanliness problem, an actual “what is this garbage on my terminal” bug report.

Setting SHELL_SESSIONS_DISABLE=1 opts the child process out of session save/restore entirely, so neither prompt fires.

TERMONMAC_SESSION={sessionId}

Each PTY session has a UUID identifier. This UUID is passed into the shell environment as TERMONMAC_SESSION.

The primary use is nested attach detection. TermOnMac supports a termonmac attach command that connects the user’s local terminal directly to a running remote session. If a user runs termonmac attach from within a session that is already managed by TermOnMac, the CLI reads TERMONMAC_SESSION and can detect the nested context:

// mac_agent/Sources/MacAgentLib/PTYSession.swift
/// PTY slave device path (e.g. /dev/ttys003). Used to detect nested attach.
private(set) var slavePath: String?

The slave path (ptsname(masterFD)) is stored for the same purpose — a CLI attaching to a session can check whether its own PTY slave path matches an existing session’s slave path.

Environment Inheritance

The child process inherits the daemon’s environment for all variables not explicitly overridden by setenv. This means:

  • HOME, USER, PATH (before profile sourcing) — inherited from daemon launch environment
  • After the login shell (-zsh) sources /etc/zprofile and ~/.zprofile, PATH is extended with Homebrew and other user-configured paths

The four explicit setenv calls override or add variables that either need specific values for correct PTY behavior (TERM, LANG) or that need to be suppressed/set for macOS-specific reasons (SHELL_SESSIONS_DISABLE, TERMONMAC_SESSION).