Skip to content

Fix misleading docs and error for ClaudeAgentOptions.user - #1233

Open
mmjerge wants to merge 1 commit into
anthropics:mainfrom
mmjerge:fix-user-option-error-and-docs
Open

Fix misleading docs and error for ClaudeAgentOptions.user#1233
mmjerge wants to merge 1 commit into
anthropics:mainfrom
mmjerge:fix-user-option-error-and-docs

Conversation

@mmjerge

@mmjerge mmjerge commented Aug 24, 2026

Copy link
Copy Markdown

Summary

ClaudeAgentOptions.user says it is an "optional user identifier associated with the session." However, this is incorrect. The value goes to subprocess.Popen(user=...). Popen resolves it with getpwnam() and calls setreuid() in the child. The CLI then runs as that OS account.

Pass an application-level ID, as the docstring suggests, and this is what comes back:

CLIConnectionError: Failed to start Claude Code: "getpwnam(): name not found: 'customer-42'"

Nothing in the message points at options.user.

History

#134 added the field to run the CLI as a custom OS user. It shipped without a docstring. #873 added docstrings in bulk and described the field as a session identifier. The published SDK reference on platform.claude.com carries the same text, so the mislabeling propagates to the official docs.

Failure modes, observed

  • Name is not an account (the common case for anyone reading the current docstring): KeyError from getpwnam(), wrapped in the generic connect error. Reproduced on macOS, Python 3.12, against this checkout.
  • Name is an account, process is not root: PermissionError.
  • Windows: ValueError; Popen does not support user there.
  • Process is root and the name matches a real account (admin, postgres): the CLI silently runs as that user, with that user's HOME and therefore that user's ~/.claude credentials and settings. No error. No log.

The change

  1. types.py — the docstring now states what the option does.
  2. subprocess_cli.pyKeyError, PermissionError, and ValueError from the spawn are translated into a CLIConnectionError that names options.user and explains the semantics. Only when the option is set; other spawn failures keep the generic message. I wrapped the failure rather than pre-validating with pwd.getpwnam(): pre-validation duplicates Popen's resolution logic and adds a TOCTOU window for nothing.
  3. Two tests in test_transport.py, next to the existing test_connect_as_different_user. The first fails on main and passes with the fix. The second pins the generic path so the new message cannot leak into unrelated failures.

Verification

  • ruff check, ruff format --check, mypy src/ scripts/: clean.
  • Full suite: 1487 passed, 5 skipped.

The setuid behavior itself is left alone. It is a deliberate feature from #134. If a warning is wanted when the switch actually happens under root, I am happy to add one.

The user option is passed to subprocess.Popen(user=...). Popen resolves
it with getpwnam() and runs the CLI as that OS account. The docstring
called it a session user identifier. Callers who believed it got:

    CLIConnectionError: Failed to start Claude Code: "getpwnam(): name
    not found: 'customer-42'"

Correct the docstring. Translate KeyError, PermissionError and
ValueError from the spawn into an error that names options.user and
explains the semantics, only when the option is set. Add regression
tests for both paths; the first fails without the fix.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant