Skip to content

[codex] Fix credential precedence and env token save#490

Merged
axisrow merged 3 commits into
mainfrom
codex/env-precedence-oauth-dotenv
Jun 1, 2026
Merged

[codex] Fix credential precedence and env token save#490
axisrow merged 3 commits into
mainfrom
codex/env-precedence-oauth-dotenv

Conversation

@axisrow

@axisrow axisrow commented Jun 1, 2026

Copy link
Copy Markdown
Owner

Summary

  • Make cwd .env / base YANDEX_DIRECT_TOKEN credentials win over the active OAuth profile when --profile is not explicitly passed.
  • Keep explicit --profile isolated from base .env login fallback.
  • Update direct auth status to report the effective credential source, including env/.env, active profile, 1Password, and Bitwarden fallbacks.
  • Add an interactive prompt to save OAuth access token/login to cwd .env.
  • Refresh README, CLAUDE.md, translations, CHANGELOG, version, and regression tests for the new contract.

Root Cause

The previous credential resolver selected the active OAuth profile before base env/cwd .env, so project-local credentials could be ignored when a global active profile existed. auth status also reported the active profile instead of the effective credentials.

Validation

  • python3 -m pytest tests/test_auth_oauth.py tests/test_env_loading.py tests/test_auth_op.py tests/test_auth_bw.py tests/test_cli.py -q
  • python3 -m pytest -m "not integration and not integration_write and not integration_live_write and not v4_live_read" -q
  • rg / git grep audit for old precedence-contract wording
  • python3 -m json.tool direct_cli/translations/auth.json and direct_cli/translations/common.json
  • python3 -m black --check direct_cli/cli.py direct_cli/commands/auth.py tests/test_auth_op.py tests/test_auth_bw.py tests/test_env_loading.py tests/test_auth_oauth.py
  • git diff --check

Fixes #489

@axisrow

axisrow commented Jun 1, 2026

Copy link
Copy Markdown
Owner Author

@claude review

@axisrow axisrow marked this pull request as ready for review June 1, 2026 13:31
Copilot AI review requested due to automatic review settings June 1, 2026 13:31
@claude

claude Bot commented Jun 1, 2026

Copy link
Copy Markdown

Claude finished @axisrow's task in 2m 51s —— View job


Review: Credential precedence & env token save

  • Read the PR diff against origin/main
  • Review direct_cli/auth.py credential precedence changes
  • Review direct_cli/commands/auth.py status/save changes
  • Verify against CLAUDE.md credential-priority contract
  • Check tests cover the new contract

Overall this is a clean, well-tested change. The refactor of get_credentials into _resolve_profile_credentials / _resolve_profile_or_raise is a nice readability win, docs/translations/CLAUDE.md are all kept in sync, and the new tests cover both precedence directions plus the .env-save prompt (yes / no / stdin-no-prompt) with a permission assertion. A few things worth addressing:


1. save_env_credentials doesn't tighten perms on an existing .env (security, low–medium)

direct_cli/auth.py:187-189

dotenv_path.write_text("".join(updated_lines), encoding="utf-8")
if not existed:
    os.chmod(dotenv_path, 0o600)

chmod 0o600 only runs when the file is newly created. If a .env already exists with looser permissions (e.g. 0644 from an editor or a prior write_text), the OAuth access token gets written into a potentially world-readable file and the permissions are left untouched. Since the whole point of this prompt is to persist a live token, I'd tighten unconditionally (or at least warn if the existing mode is broader than 0o600):

dotenv_path.write_text("".join(updated_lines), encoding="utf-8")
os.chmod(dotenv_path, 0o600)

Fix this →

A secondary, lower-severity point: the create path does write_text (default umask, often 0o644) and then chmod, leaving a brief window where the token sits with umask perms. Using os.open(path, O_WRONLY|O_CREAT|O_TRUNC, 0o600) would close that window, though for a local CLI the practical risk is small.

2. Base env token without a login no longer borrows the active profile's login (behavioral, intended?)

direct_cli/auth.py:857-870

The env-token branch only reads YANDEX_DIRECT_LOGIN when YANDEX_DIRECT_TOKEN is present, and once final_token is set the active-profile branch is skipped entirely. So with base YANDEX_DIRECT_TOKEN set but no base login, plus an active profile that has a login, the command runs with login=None rather than the profile's login. That's consistent with the "base env is self-contained, don't mix accounts" philosophy, and arguably the safest behavior — just confirming it's deliberate, since for agency tokens a missing login changes API results.

3. Headline behavior change worth a CHANGELOG/BREAKING note

Base env / cwd .env now wins over an active OAuth profile (direct auth use). A user with a leftover YANDEX_DIRECT_TOKEN exported in their shell will now silently override a profile they explicitly activated — the reverse of prior behavior. The PR documents this in README/CLAUDE.md (good), but per the repo's convention around precedence/breaking changes I'd add a CHANGELOG.md entry and consider a version bump so upgraders aren't surprised.

4. auth status "effective source" is narrower than get_credentials (minor consistency)

direct_cli/commands/auth.py:267-283

status (no --profile) reports source="env" if base env is set, else falls back to the active profile. But get_credentials also resolves 1Password / Bitwarden refs and profile-specific env vars. So in an op/bw-only setup, auth status prints No active profile. while a real command resolves fine — the reported source can disagree with what's actually used. Not blocking (status has always been profile-centric), but the command name now implies "effective credentials," so it's a slightly bigger gap than before.

5. README priority table dropped the profile-specific env-var row (doc nit)

The old table had an explicit YANDEX_DIRECT_TOKEN_AGENCY1 row. It's now folded implicitly into rows 2/4 (still works via get_env_profile), but a reader skimming the table loses the discoverability of that feature. Consider a footnote noting profile-specific env vars resolve as part of the selected/active profile.


Nits

  • _format_dotenv_value("") returns '""' — harmless since token is never empty, but worth a guard if you want to reject empty tokens explicitly.

Nothing here is a correctness blocker for the happy path; #1 is the one I'd recommend fixing before merge.
codex/env-precedence-oauth-dotenv

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adjusts Direct CLI credential resolution so project-local base env / cwd .env credentials take precedence over a global active OAuth profile unless --profile is explicitly selected, and enhances direct auth status/login to better reflect and persist the effective credentials.

Changes:

  • Reworked get_credentials() precedence: explicit --profile stays isolated; otherwise base YANDEX_DIRECT_TOKEN/.env wins over active profile.
  • Updated direct auth status to report the effective credential source and added an optional interactive flow to save token/login to cwd .env.
  • Refreshed docs, translations, and tests to match the new credential contract.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
direct_cli/auth.py Implements new credential precedence and adds .env credential writer.
direct_cli/commands/auth.py Updates auth status semantics/output and adds save-to-.env prompt after login flows.
direct_cli/cli.py Aligns early login handling with the new “explicit profile isolates base login” behavior and updates help epilog text.
direct_cli/translations/auth.json Adds/updates strings for new auth status wording and .env save prompt.
direct_cli/translations/common.json Updates help text to refer to “selected credentials” (not only OAuth profile).
README.md Documents the new credential precedence and the .env save prompt behavior (EN/RU sections).
CLAUDE.md Updates the documented credential precedence contract for CLI and tests.
tests/test_auth_oauth.py Adds regression tests for cwd .env precedence vs active profile and for .env save prompt behavior.
tests/test_cli.py Hardens CLI tests by clearing env and mocking dotenv loading to avoid leakage across tests.
tests/test_env_loading.py Adds test ensuring saving credentials creates a private-permission cwd .env.
tests/test_integration.py Updates integration-test expectations around auth status reporting effective sources.

Comment thread direct_cli/auth.py Outdated
Comment on lines +184 to +187
dotenv_path.write_text("".join(updated_lines), encoding="utf-8")
if not existed:
os.chmod(dotenv_path, 0o600)
return dotenv_path
Comment on lines 283 to 285
if not selected:
print_info(t("No active profile."))
return

axisrow commented Jun 1, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ab8385c204

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread direct_cli/auth.py Outdated
Comment on lines +156 to +157
if login:
replacements["YANDEX_DIRECT_LOGIN"] = _format_dotenv_value(login)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Clear stale env login when saving a login-less token

When the user saves a new token but resolve_account_login() returns None (for example due to a temporary network/API failure, or an imported token without --login), this branch omits YANDEX_DIRECT_LOGIN from replacements, so an existing .env keeps its old login while the token is overwritten. Subsequent commands will pair the new token with the stale client login from .env, which can target the wrong Direct client or fail unexpectedly; the save path should remove or reset the old login when no current login is known.

Useful? React with 👍 / 👎.

Comment thread direct_cli/auth.py Outdated
Comment on lines +184 to +186
dotenv_path.write_text("".join(updated_lines), encoding="utf-8")
if not existed:
os.chmod(dotenv_path, 0o600)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Tighten permissions on existing dotenv files

If the user accepts the new “Save token to .env?” prompt in a directory where .env already exists with common default permissions such as 0644, this writes the OAuth token into that world-readable file but skips chmod because existed is true. Since this path is newly storing a secret, it should set private permissions after writing regardless of whether the file was pre-existing.

Useful? React with 👍 / 👎.

axisrow commented Jun 1, 2026

Copy link
Copy Markdown
Owner Author

@codex review

axisrow commented Jun 1, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Hooray!

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@axisrow axisrow merged commit ef5b1aa into main Jun 1, 2026
6 checks passed
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.

Project .env credentials should take precedence over global auth profile

2 participants