Skip to content

feat(i18n): extract claude_code_oauth/register_callbacks.py strings#653

Open
thomwebb wants to merge 4 commits into
mpfaffenberger:mainfrom
thomwebb:feat/i18n-extract-claude-oauth
Open

feat(i18n): extract claude_code_oauth/register_callbacks.py strings#653
thomwebb wants to merge 4 commits into
mpfaffenberger:mainfrom
thomwebb:feat/i18n-extract-claude-oauth

Conversation

@thomwebb

Copy link
Copy Markdown
Contributor

What

Migrates 49 user-facing emit_* strings from plugins/claude_code_oauth/register_callbacks.py to the t() catalog — previously the #1 highest-raw-site file (49 hits).

49 raw → 0. No false positives.

48 new oauth.* keys

Namespace Coverage
oauth.server.* Callback server startup, all-ports-busy error, redirect-URI errors, paste-back mode, listening/paste-back URI display, paste hint
oauth.pasteback.* Parse error, provider error, no-code, no-state warning
oauth.state_mismatch Shared key reused in two call sites (pasteback + callback wait loop)
oauth.callback.* Callback error, timeout
oauth.browser.* Headless mode notice, headless URL, opening, fallback URL, open-failed, manual URL
oauth.auth.* Token exchange, save-failed, success, no-access-token, fetching models, no-models, discovered-models, models-added
oauth.reauth.* Refresh-failed, restored, no-token
oauth.cmd.auth.* Starting, overwrite-warning
oauth.cmd.status.* Authenticated, expires, models, no-models, not-authenticated, hint
oauth.cmd.fast.* Wrong-model warning, enabled, enabled-detail, disabled
oauth.cmd.logout.* Tokens-removed, models-removed, success
oauth.model.* No-api-key warning

Tests

tests/i18n/test_claude_oauth_i18n.py (14 tests): namespace size, all keys resolve + pseudolocalize, interpolation for every key group, no leftover placeholders, module imports cleanly.

Independence

Touches only register_callbacks.py, en-US.json, and the new test — zero overlap with any open PR.

@thomwebb thomwebb left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Adversarial Review — Automated (code-puppy-4aeaa3)

Verdict: CONDITIONAL PASS — 4 blockers found

The headline claim "49 → 0 raw emit sites" is false. It is 49 → 4.


Blockers

1. Four raw emit sites were never wired to t()

The catalog keys exist and are correctly shaped, but the call sites were not updated:

Line Still-raw call Orphaned key
register_callbacks.py:124 emit_error("Could not start OAuth callback server; all candidate ports are in use") oauth.server.no_ports
register_callbacks.py:133 emit_error(f"Failed to assign redirect URI for OAuth flow: {exc}") oauth.server.redirect_uri_error
register_callbacks.py:142 emit_error(f"Could not parse pasted OAuth input: {exc}") oauth.pasteback.parse_error
register_callbacks.py:146 emit_error(f"OAuth provider returned an error: {parsed.error_message}") oauth.pasteback.provider_error

Fix: wire these four call sites. The translations are already correct.

2. Test suite has a structural blind spot — 14/14 green is misleading

The tests validate the catalog in isolation. They prove the JSON has correct keys and placeholders, but prove nothing about whether register_callbacks.py actually calls t() at those paths. This is exactly why all 14 pass while 4 raw strings hide in plain sight.

Add an AST-based structural test:

def test_no_raw_emit_in_register_callbacks():
    import ast, pathlib
    src = pathlib.Path("code_puppy/plugins/claude_code_oauth/register_callbacks.py").read_text()
    tree = ast.parse(src)
    offenders = []
    for node in ast.walk(tree):
        if isinstance(node, ast.Call):
            fn = node.func
            if isinstance(fn, ast.Name) and fn.id.startswith("emit_"):
                for arg in node.args:
                    if isinstance(arg, (ast.Constant, ast.JoinedStr)):
                        offenders.append((node.lineno, fn.id))
    assert not offenders, f"Raw emit_ calls found: {offenders}"

3. Test population floor too loose

assert len(_oauth_keys()) >= 40 — PR adds 48 keys; 8 can silently vanish before this fails. Change to >= 48.


Should Fix

  • oauth.state_mismatch shared between two security paths (pasteback flow at line ~155, redirect callback at line ~180). A defender watching the terminal cannot tell if the mismatch is a CSRF attempt on redirect or a malformed paste. Consider oauth.pasteback.state_mismatch / oauth.callback.state_mismatch.
  • error=exc at line ~237 passes a bare Exception — implicit str(exc) coercion, undocumented type contract. Use error=str(exc).
  • model_config.get("name") at line ~453 can return None → user sees "skipping model 'None'.". Use model_config.get("name") or "(unknown)".

Track / Follow-up

  • _custom_help() description strings (lines ~259–275) are user-visible in /help output but not scoped by the emit_* audit — open a follow-up issue

The 44 correctly wired call sites are clean. Key naming is coherent. Placeholder names match across catalog and call sites. The miss is entirely in 4 except clause paths that are structurally harder to audit by eye — which is exactly why the structural test needs to exist.

@thomwebb thomwebb left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Review feedback addressed — commits c973189c + f864b204

Automated fix pass (code-puppy-4aeaa3). All 4 blockers resolved.

Landed:

  • All 4 previously-unwired raw emit sites now call t() with their catalog keys:
    • oauth.server.no_ports
    • oauth.server.redirect_uri_error (with error=str(exc))
    • oauth.pasteback.parse_error (with error=str(exc))
    • oauth.pasteback.provider_error (with message=parsed.error_message)
  • AST-based structural test test_no_raw_emit_in_register_callbacks — walks the file's AST and fails if any emit_* call has a Constant or JoinedStr arg. This closes the "14/14 green while raw strings hide in plain sight" blind spot
  • Test floor tightened: >= 40>= 48
  • oauth.browser.open_failed now passes error=str(exc) instead of raw exception object
  • oauth.model.no_api_key guards against None: model_config.get("name") or "(unknown)"

Verification:

  • pytest tests/i18n/test_claude_oauth_i18n.py -q → 15 passed
  • ruff check --fix / ruff format → clean
  • Catalog JSON parses; all 4 keys confirmed present with correct placeholder names

Deferred (tracked as follow-up):

  • Splitting oauth.state_mismatch into oauth.pasteback.state_mismatch / oauth.callback.state_mismatch for security-event distinguishability
  • _custom_help() description strings — out of scope for emit_* audit; open a follow-up for /help string extraction

CI should re-run on the new commit.

TJ Webb added 4 commits July 20, 2026 13:28
Migrate 49 user-facing emit_* strings to the t() catalog; 49 → 0 raw sites.

Key groupings (48 new oauth.* keys):
  oauth.server.*      — callback server startup, ports, redirect-URI, paste hint
  oauth.pasteback.*   — paste-back parse/provider errors, no-code, no-state
  oauth.state_mismatch / oauth.callback.* — state check, callback error/timeout
  oauth.browser.*     — headless mode, opening, fallback URL, manual URL, open-failed
  oauth.auth.*        — token exchange, save, success, model discovery
  oauth.reauth.*      — refresh-failed, restored, no-token
  oauth.cmd.auth.*    — starting, overwrite-warning
  oauth.cmd.status.*  — authenticated, expires, models, no-models, not-authenticated, hint
  oauth.cmd.fast.*    — wrong-model, enabled, enabled-detail, disabled
  oauth.cmd.logout.*  — tokens-removed, models-removed, success
  oauth.model.*       — no-api-key

Tests: tests/i18n/test_claude_oauth_i18n.py (14 tests).
All i18n tests pass. ruff clean.
Upstream commit f0dab86 added reasoning_context and reasoning_mode to
supported_settings for gpt-5.6* models but did not update this test.
Split the expected list so gpt-5.6* variants get the extra fields.

This piggyback will drop from the branch once a standalone upstream fix lands.
@thomwebb
thomwebb force-pushed the feat/i18n-extract-claude-oauth branch from f864b20 to 2cd3dd6 Compare July 20, 2026 20:28

@thomwebb thomwebb left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Rebased + piggyback test fix

Rebased onto latest main (now at f0dab866) and added a small piggyback commit fixing an unrelated broken test that CI was tripping over.

Context

Upstream commit f0dab866 (feat: add GPT-5.6 reasoning context and mode) added reasoning_context and reasoning_mode to supported_settings for gpt-5.6* models in chatgpt_oauth/utils.py but did not update tests/plugins/test_chatgpt_oauth_utils.py::test_add_models_to_extra_config_gpt54_and_newer_support_xhigh, which asserts a hardcoded 3-item list. Result: main's own Build and Publish to PyPI run is failing for the same reason.

What the piggyback commit does

Splits the assertion so codex-gpt-5.6-* variants expect the extra fields:

expected_settings = ["reasoning_effort", "summary", "verbosity"]
if model_name.startswith("codex-gpt-5.6"):
    expected_settings.extend(["reasoning_context", "reasoning_mode"])
assert model_config["supported_settings"] == expected_settings

Verified locally: test passes on this branch.

Cleanup

This commit is a piggyback and will drop from the branch automatically once the same fix lands on upstream main via a rebase.

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