Skip to content

docs: Added CLAUDE.md with AGENTS.md and references - #141

Open
rmad17 wants to merge 9 commits into
mainfrom
feat/claude-md
Open

rmad17 wants to merge 9 commits into
mainfrom
feat/claude-md

Conversation

@rmad17

@rmad17 rmad17 commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds agent guidelines for this SDK: a CLAUDE.md with project structure, boundaries, and security considerations, an AGENTS.md that points to it, and a references/ folder with the detail behind each section. No library code changes.

Changes

  • Added CLAUDE.md covering the role, working principles, project structure, Always Do / Ask First / Never Do boundaries, and security considerations (algorithm pinning, token-derived hosts, unverified JWT values, organization policy scope, DPoP, token exchange, token store caching and cache keys, verified= trust boundary, error handling).
  • Added AGENTS.md, which references CLAUDE.md.
  • Added references/ files, read on demand from CLAUDE.md:
    • commands.md: CI-matching install, test, and lint commands
    • testing.md: fixtures, async test patterns, coverage
    • code-style.md: naming, error patterns, comment rules
    • flow-map.md: entry points, helpers, tests, and docs per flow
    • docs-update.md: tracked docs and a code-to-docs mapping
    • git-workflow.md: branches, commits, PR template, CI workflows
    • pitfalls.md: common failures

Testing

No source, tests, or dependencies changed, so there is no runtime or test impact.

@rmad17
rmad17 requested a review from a team as a code owner October 12, 2026 15:26
rmad17 and others added 4 commits October 28, 2026 23:28
Comment thread CLAUDE.md Resolved
Comment thread CLAUDE.md Resolved
Comment thread CLAUDE.md Resolved
Comment thread CLAUDE.md Resolved
Comment thread CLAUDE.md Resolved
Comment thread AGENTS.md
@@ -0,0 +1,3 @@
# AI Agent Guidelines for auth0-api-python

@./CLAUDE.md for all coding guidelines, commands, project structure, code style, testing conventions, and boundaries.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The @./CLAUDE.md import is Claude Code syntax. Other tools that read AGENTS.md may show the line as plain text and never load CLAUDE.md. There is also a feat/add-agents-file branch on the repo with a different AGENTS.md (a guide for integrators), so it might be worth checking the two don't clash.

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.

The feat/add-agents-file branch is very old and stale. Better to use the new one as a cleaner approach.

@sanchitmehtagit sanchitmehtagit Nov 5, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

https://developer.android.com/studio/gemini/agent-files#modularize-agent-files Gemini does support the syntax @./CLAUDE.md. Codex doesnt call out this syntax as supported there was open gh issue too on this, somehow using codex cli ,llm was able to infer it and it made a read tool call just as how reference file work and not as inline instructions when i tested it..
I think we would anyways have single agents.md from now on we would not need to maintain multiple files @rmad17 you can raise a new PR after this with only agents.md. i have also updated the skill to only generate agents.md

Comment thread CLAUDE.md Resolved
Comment thread references/code-style.md Resolved
Comment thread CLAUDE.md Resolved
Comment thread CLAUDE.md Resolved
Comment thread CLAUDE.md
utils.py # normalize_domain, OIDC/JWKS fetch, unverified header/payload decode, htu normalization
act.py # get_current_actor / get_delegation_chain (RFC 8693 act claim)
types.py # TypedDicts: DomainsResolverContext, OnBehalfOfTokenResult, ClientCredentialsTokenResult, ActClaim. DomainsResolver callable alias
token_utils.py # Test helpers (token/DPoP proof generators) with a synthetic test key. Not in __all__, but packaged and importable

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

telemetry.py (added in #153, it builds the Auth0-Client and User-Agent headers and validates client_info) isn't in this list of source files.

Shall we add a line for it?

Comment thread CLAUDE.md
token_utils.py # Test helpers (token/DPoP proof generators) with a synthetic test key. Not in __all__, but packaged and importable
tests/
conftest.py # Shared fixtures (api_client_confidential, mock_discovery) and httpx mock helpers
test_api_client.py, test_cache.py, test_utils.py, test_act.py, test_token_store.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

tests/test_telemetry.py is missing from this list, and the conftest line above doesn't mention the new auth0_client_payload and assert_telemetry_headers helpers. The test lists in testing.md and flow-map.md, and the helper list in testing.md, have the same gap.

Shall we update them together?

Comment thread CLAUDE.md
- Make surgical changes. Touch only what the request requires. Do not refactor or reformat adjacent code.
- Follow existing style and naming. Add new public `ApiClient` methods after the existing public methods, above the `# ===== Private Methods =====` banner in `api_client.py`. Add new private helpers below it. Never reorder existing declarations.
- Add unit tests for new functionality, marked `@pytest.mark.asyncio` when async, mocking HTTP with `pytest-httpx` (no real network calls).
- Raise typed errors from `errors.py` (subclasses of `BaseAuthError` with `get_status_code()` / `get_error_code()`). Wrap third-party failures in a typed error with `raise ... from e`. Inside a catch-all, re-raise this SDK's own errors first (`except VerifyAccessTokenError: raise`, as `verify_access_token` does) so they are not double-wrapped. Never return `None` or a bare `dict` to signal failure, and never leak raw `httpx`/`authlib` errors from public methods.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This says to never let raw httpx errors out of public methods, but the next bullet says a cold-cache discovery outage does exactly that in all three token methods, and the README notes it for the cases where verification is skipped. The 'Fail open' line further down reads the same way.

Shall we say 'in new code' here so it doesn't read as already true everywhere?

Comment thread CLAUDE.md
- Follow existing style and naming. Add new public `ApiClient` methods after the existing public methods, above the `# ===== Private Methods =====` banner in `api_client.py`. Add new private helpers below it. Never reorder existing declarations.
- Add unit tests for new functionality, marked `@pytest.mark.asyncio` when async, mocking HTTP with `pytest-httpx` (no real network calls).
- Raise typed errors from `errors.py` (subclasses of `BaseAuthError` with `get_status_code()` / `get_error_code()`). Wrap third-party failures in a typed error with `raise ... from e`. Inside a catch-all, re-raise this SDK's own errors first (`except VerifyAccessTokenError: raise`, as `verify_access_token` does) so they are not double-wrapped. Never return `None` or a bare `dict` to signal failure, and never leak raw `httpx`/`authlib` errors from public methods.
- Route discovery and JWKS fetches through `ApiClient._discover` / `_fetch_jwks`, which call `fetch_oidc_metadata` / `fetch_jwks`, so both `custom_fetch` and caching apply. Token endpoint calls use `httpx.AsyncClient(timeout=httpx.Timeout(self.options.timeout))`, map `httpx.TimeoutException` to `ApiError` 504 and `httpx.HTTPError` to 502, and reject a negative `expires_in` with `ApiError("invalid_response", ..., 502)`. Copy `get_token_by_exchange_profile` for error-body and invalid-JSON handling, since it guards the error parse and uses 502. `get_access_token_for_connection` calls `response.json()` on the error path unguarded and raises `invalid_json` with the default status 500. In all three token methods `_discover()` runs before the `try`, so a discovery outage with a cold cache raises a raw `httpx` error instead of an `ApiError`. A discovery or JWKS outage during verification (connection error, timeout, HTTP 5xx or 429, classified by `is_auth_server_outage`) raises `AuthServerUnavailableError` (503), not a 401.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Since #153, every request the default HTTP client sends passes headers=self._telemetry_headers (discovery, JWKS and the three token endpoint calls). This bullet describes how to write a token endpoint call but doesn't mention it, so a new call written from this guidance would go out without the Auth0-Client and User-Agent headers.

Shall we add it here?

Comment thread CLAUDE.md
- Follow existing style and naming. Add new public `ApiClient` methods after the existing public methods, above the `# ===== Private Methods =====` banner in `api_client.py`. Add new private helpers below it. Never reorder existing declarations.
- Add unit tests for new functionality, marked `@pytest.mark.asyncio` when async, mocking HTTP with `pytest-httpx` (no real network calls).
- Raise typed errors from `errors.py` (subclasses of `BaseAuthError` with `get_status_code()` / `get_error_code()`). Wrap third-party failures in a typed error with `raise ... from e`. Inside a catch-all, re-raise this SDK's own errors first (`except VerifyAccessTokenError: raise`, as `verify_access_token` does) so they are not double-wrapped. Never return `None` or a bare `dict` to signal failure, and never leak raw `httpx`/`authlib` errors from public methods.
- Route discovery and JWKS fetches through `ApiClient._discover` / `_fetch_jwks`, which call `fetch_oidc_metadata` / `fetch_jwks`, so both `custom_fetch` and caching apply. Token endpoint calls use `httpx.AsyncClient(timeout=httpx.Timeout(self.options.timeout))`, map `httpx.TimeoutException` to `ApiError` 504 and `httpx.HTTPError` to 502, and reject a negative `expires_in` with `ApiError("invalid_response", ..., 502)`. Copy `get_token_by_exchange_profile` for error-body and invalid-JSON handling, since it guards the error parse and uses 502. `get_access_token_for_connection` calls `response.json()` on the error path unguarded and raises `invalid_json` with the default status 500. In all three token methods `_discover()` runs before the `try`, so a discovery outage with a cold cache raises a raw `httpx` error instead of an `ApiError`. A discovery or JWKS outage during verification (connection error, timeout, HTTP 5xx or 429, classified by `is_auth_server_outage`) raises `AuthServerUnavailableError` (503), not a 401.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This bullet is around 125 words and mixes discovery routing, token endpoint calls, error body handling and outage mapping.

Shall we split it into sub-bullets, like the token store section below?


## Releases

`.version` is the version source. `.shiprc` keeps `pyproject.toml` in step with it. Releases are cut by `publish.yml` (manual dispatch, gated on the RL scan), not by an agent editing version files. Nothing in `.github/workflows/`, `.github/actions/`, or `.shiprc` writes `CHANGELOG.md`. Release PRs change it together with the version files.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

publish.yml also creates the GitHub release, so 'cut by publish.yml' covers more than the publish step. It might be worth a half sentence, since this is where an agent would look to see what a release touches.

Comment thread references/pitfalls.md
# Common Pitfalls

1. **Do not run `ruff format .`.** CI runs only `ruff check .`. The codebase is not ruff-formatted, so a repo-wide format produces a large unrelated diff. Match surrounding formatting by hand.
2. **Python floor compatibility.** The floor is the `python` constraint under `[tool.poetry.dependencies]` in `pyproject.toml`. `.ruff.toml` `target-version` and the `test.yml` matrix hard-code their own versions and do not follow it, so check all three agree, and raise a mismatch rather than fixing CI or ruff config yourself (Ask First). The CI matrix pins the minor version only, so it tests the latest patch release, not the exact floor patch. Avoid syntax newer than the floor. While it is below 3.10, that means no `X | Y` type unions at runtime and no `match` statements. `ada-url` has separate version constraints above and below 3.10 in `pyproject.toml`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This item is around 100 words and covers the floor definition, the three places that carry the version, and the 3.10 syntax limits.

Shall we split it into sub-points?

Comment thread references/pitfalls.md
1. **Do not run `ruff format .`.** CI runs only `ruff check .`. The codebase is not ruff-formatted, so a repo-wide format produces a large unrelated diff. Match surrounding formatting by hand.
2. **Python floor compatibility.** The floor is the `python` constraint under `[tool.poetry.dependencies]` in `pyproject.toml`. `.ruff.toml` `target-version` and the `test.yml` matrix hard-code their own versions and do not follow it, so check all three agree, and raise a mismatch rather than fixing CI or ruff config yourself (Ask First). The CI matrix pins the minor version only, so it tests the latest patch release, not the exact floor patch. Avoid syntax newer than the floor. While it is below 3.10, that means no `X | Y` type unions at runtime and no `match` statements. `ada-url` has separate version constraints above and below 3.10 in `pyproject.toml`.
3. **Async tests need `@pytest.mark.asyncio`.** No `asyncio_mode` is set in `pyproject.toml`, so pytest-asyncio runs in strict mode and ignores an unmarked async test. Depending on the pytest version it then fails with an "async def functions are not natively supported" message or is skipped with a warning. If a new async test fails that way or shows as skipped, check the marker first.
4. **Two dependency lists.** `pyproject.toml` + `poetry.lock` drive installs. `requirements.txt` is a hand-maintained partial list (`authlib`, `httpx`, `ada-url`, and the pytest packages, without `cryptography`, `jwcrypto`, `requests` or `ruff`) that only the Snyk SCA workflow reads. Changing one without the other leaves the scan out of date.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

requirements.txt also lists freezegun, so 'the pytest packages' leaves one out of what the file covers.

Comment thread references/pitfalls.md
2. **Python floor compatibility.** The floor is the `python` constraint under `[tool.poetry.dependencies]` in `pyproject.toml`. `.ruff.toml` `target-version` and the `test.yml` matrix hard-code their own versions and do not follow it, so check all three agree, and raise a mismatch rather than fixing CI or ruff config yourself (Ask First). The CI matrix pins the minor version only, so it tests the latest patch release, not the exact floor patch. Avoid syntax newer than the floor. While it is below 3.10, that means no `X | Y` type unions at runtime and no `match` statements. `ada-url` has separate version constraints above and below 3.10 in `pyproject.toml`.
3. **Async tests need `@pytest.mark.asyncio`.** No `asyncio_mode` is set in `pyproject.toml`, so pytest-asyncio runs in strict mode and ignores an unmarked async test. Depending on the pytest version it then fails with an "async def functions are not natively supported" message or is skipped with a warning. If a new async test fails that way or shows as skipped, check the marker first.
4. **Two dependency lists.** `pyproject.toml` + `poetry.lock` drive installs. `requirements.txt` is a hand-maintained partial list (`authlib`, `httpx`, `ada-url`, and the pytest packages, without `cryptography`, `jwcrypto`, `requests` or `ruff`) that only the Snyk SCA workflow reads. Changing one without the other leaves the scan out of date.
5. **`custom_fetch` covers only discovery and JWKS.** `fetch_oidc_metadata` and `fetch_jwks` (`utils.py`) use the integrator's `custom_fetch` when set. Otherwise they create a plain `httpx.AsyncClient()` with no timeout, so `options.timeout` does not apply to them. The token endpoint calls in `get_access_token_for_connection`, `get_token_by_exchange_profile`, and `get_client_credentials_token` always build their own `httpx.AsyncClient` with `options.timeout`. Do not assume a new HTTP path inherits either behaviour, pick the matching pattern deliberately.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This says the plain httpx.AsyncClient() has no timeout, but httpx 0.28.1 (the version in poetry.lock) applies a 5 second default when none is given. What doesn't apply to these two fetches is options.timeout. The 'no timeout' wording came from a comment I left on CLAUDE.md earlier, and it was wrong.

Shall we say they use the httpx default of 5 seconds and that options.timeout doesn't apply to them?

Comment thread references/pitfalls.md
2. **Python floor compatibility.** The floor is the `python` constraint under `[tool.poetry.dependencies]` in `pyproject.toml`. `.ruff.toml` `target-version` and the `test.yml` matrix hard-code their own versions and do not follow it, so check all three agree, and raise a mismatch rather than fixing CI or ruff config yourself (Ask First). The CI matrix pins the minor version only, so it tests the latest patch release, not the exact floor patch. Avoid syntax newer than the floor. While it is below 3.10, that means no `X | Y` type unions at runtime and no `match` statements. `ada-url` has separate version constraints above and below 3.10 in `pyproject.toml`.
3. **Async tests need `@pytest.mark.asyncio`.** No `asyncio_mode` is set in `pyproject.toml`, so pytest-asyncio runs in strict mode and ignores an unmarked async test. Depending on the pytest version it then fails with an "async def functions are not natively supported" message or is skipped with a warning. If a new async test fails that way or shows as skipped, check the marker first.
4. **Two dependency lists.** `pyproject.toml` + `poetry.lock` drive installs. `requirements.txt` is a hand-maintained partial list (`authlib`, `httpx`, `ada-url`, and the pytest packages, without `cryptography`, `jwcrypto`, `requests` or `ruff`) that only the Snyk SCA workflow reads. Changing one without the other leaves the scan out of date.
5. **`custom_fetch` covers only discovery and JWKS.** `fetch_oidc_metadata` and `fetch_jwks` (`utils.py`) use the integrator's `custom_fetch` when set. Otherwise they create a plain `httpx.AsyncClient()` with no timeout, so `options.timeout` does not apply to them. The token endpoint calls in `get_access_token_for_connection`, `get_token_by_exchange_profile`, and `get_client_credentials_token` always build their own `httpx.AsyncClient` with `options.timeout`. Do not assume a new HTTP path inherits either behaviour, pick the matching pattern deliberately.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The README (section 10) says requests served by custom_fetch don't carry the telemetry headers either, which this pitfall could mention alongside options.timeout.

Also, 'Do not assume a new HTTP path inherits either behaviour, pick the matching pattern deliberately' joins two clauses with a comma.

This branch has not been deployed

No deployments
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.

3 participants