frontapp-openapi-client
CLAUDE.md0 starsChanged 7 months agoMITArchived repositoryView on GitHub
Guidance for Claude Code working with this repository. NEVER CANCEL long-running commands - they may appear to hang but are processing. For routine factual questions about the generated API surface, read docs/api-facts.yaml before grepping the api/ tree. It is generated by scripts/generate_api_facts.py and validated in CI by uv run poe facts-check, so it cannot drift from the generated code. The file pre-computes the most common queries: - "Does list_X return field_results, a raw array, or a single object?" → summary.list_endpoints_with_field_results…
- Reads credentials
- Installs packages
- Commits and pushes
In this repository6 other files it gives its agents.
- .github/copilot-instructions.mdCopilot instructions
- babysit-prsSkill
- new-verticalSkill
- open-prSkill
- review-prSkill
- vendor-and-regenSkill
The file
Show its rules and text
We split this CLAUDE.md into the rules it gives an agent
14 rules. About 0 are common, 6 are distinctive, and 8 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈6
Distinctive · estimate
This author's own choices. Worth a look.
8
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 33
docs/api-facts.yaml: before grepping the api/ tree. - Very specific: line 50
When the answer isn't in the facts file (rare — check summary.* first), fall back to direct inspection of …
- Very specific: line 72
Always run the appropriate validation tier before considering work complete.
- Distinctive: line 82
Fix a tricky bug?: Add the root cause to Known Pitfalls.
- Distinctive: line 83
Discover a new anti-pattern?: Add it to Anti-Patterns to Avoid.
- Distinctive: line 84
Find a command or workflow that's missing?: Add it to Essential Commands or Detailed Documentation.
- Distinctive: line 86
Hit a confusing API behavior?: Document it so the next session doesn't waste time rediscovering it.
- Distinctive: line 94
This is a living document: When you discover a new recurring mistake, surprising API behavior, or gotcha during development, add it here so …
- Very specific: line 182
If LSP <op> returns No LSP server available for file type: .py (or .ts), the plugin isn't enabled or the binary isn't reachable — verify …
- Very specific: line 222
If the question is "where is the string foo in this repo?" — Grep.
- Very specific: line 233
If hover returns Unknown for a _project-external_ import (e.g., pydantic, fastmcp), the langserver is stale — flag to the user that Claude …
- Very specific: line 299
Use ! for breaking changes: feat(client)!: breaking change
- Very specific: line 303
Use the helper utilities in frontapp_public_api_client/utils.py for consistent response handling:
- Distinctive: line 413
Discover on-demand: - read these when working on specific areas:
≈ “Common” is an estimate: at least 1 in 5 of the 26,980 CLAUDE.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
CLAUDE.md
Guidance for Claude Code working with this repository.
Quick Start
uv sync --all-extras # Install dependencies
uv run pre-commit install # Setup hooks
cp .env.example .env # Add FRONTAPP_API_KEYEssential Commands
| Command | Time | When to Use |
uv run poe quick-check | ~5-10s | During development |
uv run poe agent-check | ~8-12s | Before committing |
uv run poe check | ~30s | Before opening PR |
uv run poe full-check | ~40s | Before requesting review |
uv run poe fix | ~5s | Auto-fix lint issues |
uv run poe test | ~16s | Run tests (4 workers) |
uv run python scripts/vendor_spec.py | ~3s | Refresh spec from upstream Front repo |
uv run poe regenerate-client | ~1-2min | Regenerate Python client from spec |
uv run poe facts | ~3s | Regenerate docs/api-facts.yaml |
pnpm --filter frontapp-client generate | ~5s | Regenerate TypeScript client |
NEVER CANCEL long-running commands - they may appear to hang but are processing.
API Facts Index — Read this first
For routine factual questions about the generated API surface, read
docs/api-facts.yaml before grepping the api/ tree. It is generated by
scripts/generate_api_facts.py and validated in CI by uv run poe facts-check, so it
cannot drift from the generated code.
The file pre-computes the most common queries:
- "Does
list_Xreturnfield_results, a raw array, or a single object?" →summary.list_endpoints_with_field_results/summary.list_endpoints_returning_raw_array - "Which endpoints are mutations (need two-step confirm)?" →
summary.mutations - "Which generated module names have name quirks (
_a_infix,removes_prefix)?" →summary.module_name_quirks - "Is there already a helper / domain projection for resource X?" →
tags.<resource>.helper.built/tags.<resource>.domain.built - "What's the response model class name for
list_X?" →tags.<resource>.endpoints[].response_type
When the answer isn't in the facts file (rare — check summary.* first), fall back to
direct inspection of frontapp_public_api_client/api/<tag>/. Always prefer the facts
file for context efficiency: one Read returns answers that would otherwise require
dozens of file inspections.
CRITICAL: Zero Tolerance for Ignoring Errors
FIX ALL ISSUES. NO EXCEPTIONS.
- NO
noqa,type: ignore, exclusions, or skips - NO "pre-existing issues" or "unrelated to my changes" excuses
- NO
--no-verifycommits - ASK for help if blocked - don't work around errors
Proper fixes:
- Too many parameters? → Create a dataclass
- Name shadows built-in? → Rename it
- Circular import? → Use
TYPE_CHECKINGblock
Verify Your Work
Always run the appropriate validation tier before considering work complete. See the
Essential Commands table above - use quick-check during development, agent-check
before committing, and check before opening a PR. Don't trust that code works just
because it looks right.
Continuous Improvement
This file is meant to evolve. Update it when you learn something that would help future sessions:
- Fix a tricky bug? Add the root cause to Known Pitfalls.
- Discover a new anti-pattern? Add it to Anti-Patterns to Avoid.
- Find a command or workflow that's missing? Add it to Essential Commands or Detailed Documentation.
- Hit a confusing API behavior? Document it so the next session doesn't waste time rediscovering it.
The same applies to all project documentation - if instructions are wrong, incomplete, or misleading, fix them as part of your current work rather than leaving them for later.
Known Pitfalls
This is a living document. When you discover a new recurring mistake, surprising API behavior, or gotcha during development, add it here so future sessions don't repeat it.
Common mistakes to avoid:
- Editing generated files —
api/**/*.py,models/**/*.py, andclient.pyare generated. Runuv run poe regenerate-clientinstead of editing them directly. - Editing the spec in-place —
docs/frontapp-openapi.yamlis vendored and sanitized. To pull upstream changes runuv run python scripts/vendor_spec.py, not a direct edit. If upstream adds a quirk that breaks codegen, patch the sanitization rules inscripts/vendor_spec.pyrather than hand-editing the YAML. - UNSET vs None confusion — attrs model fields that are unset use a sentinel value,
not
None. Useunwrap_unset(field, default)fromfrontapp_public_api_client.domain.converters, notisinstanceorhasattrchecks. - Manual status code checks — Don't write
if response.status_code == 200. Useunwrap_as(),unwrap_data(), oris_success()fromfrontapp_public_api_client.utils. - Pagination: cursor tokens — Front uses cursor-based pagination. List responses
include
_pagination.nextas a full URL; pass thepage_tokenquery parameter back to the list endpoint to fetch the next page.limitcaps page size (max 100, default 50). Nototalcount is returned. - Front search syntax (
q=parameter) — Conversation list/search endpoints accept Front's query DSL:status:open tag:urgent assignee:me before:2024-12-31. Not every filter is a separate query param — they're all in theq=string. - Generated list-response field names — openapi-python-client prefixes fields with
field_when they'd otherwise collide with Python (leading-underscore names). So_resultsbecomesfield_results,_paginationbecomesfield_pagination. Always usegetattr(parsed, "field_results", None) or []when unwrapping list responses. Which list endpoints usefield_resultsvs return raw arrays is enumerated indocs/api-facts.yamlundersummary.list_endpoints_with_field_resultsandsummary.list_endpoints_returning_raw_array. Front's spec is currently all-field_results(the raw-array set is empty); the facts file is the authoritative source if that ever changes. - Front error responses have no single schema — Front doesn't define a canonical
ErrorResponsetype; each endpoint models errors inline.utils.unwrap()dispatches on HTTP status code, not on parsed-typeisinstancechecks. Don't try to import anErrorResponsemodel frommodels/; there isn't one. - None-to-UNSET conversion — When building attrs API request models from optional
fields, use
to_unset(value)fromfrontapp_public_api_client.domain.convertersinstead ofvalue if value is not None else UNSET. - Help resource drift —
frontapp_mcp_server/.../resources/help.pycontains hardcoded tool documentation. When adding or modifying tool parameters, also update the help resource content to stay in sync. typecheckskips generated code deliberately —ty checkonly runs over hand-written paths (helpers/,domain/,frontapp_client.py,utils.py). The generatedapi/andmodels/have ~9 false positives from openapi-python-client'sfrom_dictpatterns that ty (pre-1.0) doesn't narrow correctly. When adding new hand-written modules, extend thetypechecktask path list inpyproject.tomlto include them. Revisit the exclusion when ty hits 1.0 — tracked in issue #8.git push -u origin <branch>can land on main — when a feature branch is created viagit checkout -b <name> origin/main, its upstream isorigin/main, so a baregit push -u origin <name>resolves to main and bypasses PR review. Thescripts/pre-push-guard.shpre-commit hook blocks this. The right form for first-time pushes isgit push -u origin HEAD:refs/heads/<branch-name>.--no-verifyis forbidden — fix the push command instead.uv.lockdrift you didn't cause — when you start a branch off an oldermainand a sibling-package release (e.g.mcp-vX.Y.Z) lands onmainwhile you work, youruv.lockwill show modifications you didn't make once you sync or runuv sync. Stage and commit it (git add uv.lock) bundled with your other work — don't try to revert it. The lockfile is authoritative and CI re-resolves frompyproject.tomlanyway, so a stale lockfile causes spurious CI diffs.- Generator/script edits without committing the regen — when you edit
scripts/vendor_spec.py,scripts/regenerate_client.py, orscripts/generate_api_facts.py, you must also run the corresponding regenerate task and commit the regenerated output.block-generated-edits.shblocks direct edits to generated files but doesn't catch this case (the generator changes look unrelated). CI will fail onfacts-checkor generated-client diff if you forget. - Live Front API data must not enter the repo — when testing against a real Front
workspace, never commit raw response fixtures, names, emails, or conversation bodies
from real customers. Sanitize to placeholders (
cnv_TEST,customer@example.com, generic message bodies) before saving as a test fixture or example. Front workspaces contain customer PII and the published client is a public package.
Using the LSP tool
Both Python (pyright) and TypeScript (typescript-language-server) LSPs are wired through
Claude Code plugins (pyright-lsp@claude-plugins-official and
typescript-lsp@claude-plugins-official, enabled in .claude/settings.json →
enabledPlugins). The plugins only register the language servers — the binaries
themselves must be installed and on PATH:
uv tool install pyright # pyright + pyright-langserver → ~/.local/bin
npm install --prefix ~/.local typescript-language-server typescript
# then symlink both binaries into a directory already on PATH (e.g. ~/bin/)If LSP <op> returns No LSP server available for file type: .py (or .ts), the
plugin isn't enabled or the binary isn't reachable — verify which pyright-langserver /
which typescript-language-server and restart Claude Code after enabling the plugin.
Prefer LSP operations over Read + Grep for type and call-graph questions — they
are faster, more accurate, and cross-reference the real type system (including
third-party libraries in .venv).
| When you need to… | Use |
| Understand a symbol's type/signature/docstring | LSP hover |
| Jump to where a function/class is defined (project code) | LSP goToDefinition |
| Find every caller of a function before changing its signature | LSP findReferences |
| List all symbols in a file (skim without reading all of it) | LSP documentSymbol |
| Trace callers of a function (who calls X?) | LSP incomingCalls |
| Trace callees of a function (what does X call?) | LSP outgoingCalls |
Project root must match workspace root. The pyright config lives at
pyrightconfig.json (relative venvPath: "."). CLI pyright uses it automatically
(npx pyright or uv run pyright); the langserver reads it on startup.
Reach for LSP first in these situations
It's easy to fall back on Grep reflexively — these are the situations where doing so
is genuinely worse than the LSP:
- Before extracting a helper, renaming a symbol, or changing a function signature —
LSP findReferencesreturns the precise call graph. Grepping the symbol name misses re-exports, dynamic dispatch, and string-only matches in unrelated docs; LSP doesn't. - Before designing a class that mirrors an existing one —
LSP documentSymbolon the canonical file (e.g.helpers/contacts.py) gives you the full method list in one hop;Readwalks the whole file. - When debugging a
'X' object has no attribute 'y'style error —LSP hoveron the access tells you whether the type isUnset,None, a domain model, or a third-party class. Grepping for the field misses the type relationship. - Refactors that touch >10 files — get the call graph from
LSP findReferences, then mechanically rewrite. Lesson from PR #71 (confirm_or_previewrollout): a grep-driven refactor missed several formatting variants the regex didn't catch and needed a hand-edit pass to clean up.
If the question is "where is the string foo in this repo?" — Grep. If the question
is "what calls / extends / overrides / shadows foo?" — LSP.
LSP known limitations
workspaceSymbolreturns nothing in this tooling — pyright only indexes open files, and the LSP tool doesn't expose the query parameter. UseGrepfor project-wide symbol search instead (e.g.,Grep "def format_md_table").goToImplementationis not implemented by pyright — usegoToDefinitioninstead.goToDefinitionon external-library imports returns "no definition found" — usehoverinstead, which gives you the class signature + docstring.- If
hoverreturnsUnknownfor a project-external import (e.g., pydantic, fastmcp), the langserver is stale — flag to the user that Claude Code needs a restart to re-readpyrightconfig.json. All project-internal imports should always resolve.
Architecture Overview
Monorepo with 3 packages:
frontapp_public_api_client/— Python client with transport-layer resilience and hand-written domain helpers (client.conversations.…+ more as verticals ship)frontapp_mcp_server/— FastMCP server exposing Front operations as AI toolspackages/frontapp-client/— TypeScript client (hey-api generated + transport middleware for retries and pagination)
API scope: Front's Core API — 139 paths, 233 generated operations across conversations, messages, contacts, teammates, tags, inboxes, channels, rules, analytics, etc. Bearer auth on every endpoint.
Key patterns:
- Transport-layer resilience — retries, rate-limit awareness live in the httpx
transport stack, not on individual methods. Every endpoint inherits them automatically
via
FrontappClient. - Two-layer API surface — direct
from frontapp_public_api_client.api.<tag> import <operation>access to every generated endpoint, plus hand-writtenclient.<resource>.…facades that return Pydantic domain models for the most-used resources. - Two-step confirm on MCP mutations — tools like
update_conversationandcreate_draft_replytake aconfirm: bool = Falseparameter;confirm=Falsereturns a preview,confirm=Trueboth executes and elicits explicit user approval viactx.elicit. Outbound replies always go through the drafts vertical (create_draft_reply,create_draft_on_channel, etc.) — there is no direct-send tool. See ADR-0016 → "Drafts-first outbound". - Spec sanitization at vendor time — Front's upstream OpenAPI spec has a few quirks
that break openapi-python-client (
examplevalues on required path params, allOf-inherited defaults, binary*/*attachment downloads).scripts/vendor_spec.pydownloads and patches before writingdocs/frontapp-openapi.yaml.
File Rules
| Category | Files | Action |
| EDITABLE | frontapp_public_api_client/{frontapp_client.py, helpers/**, domain/**, utils.py}, frontapp_mcp_server/src/**, packages/frontapp-client/src/**, tests/**, frontapp_mcp_server/tests/**, scripts/**, docs/** (except the YAMLs below), .claude/**, .github/** | Can modify |
| GENERATED — DO NOT EDIT | frontapp_public_api_client/api/**/*.py, frontapp_public_api_client/models/**/*.py, frontapp_public_api_client/client.py, frontapp_public_api_client/client_types.py, frontapp_public_api_client/errors.py, docs/frontapp-openapi.yaml (vendored from upstream), docs/api-facts.yaml (generated from the api/ tree), packages/frontapp-client/src/generated/** (TS) | DO NOT EDIT |
The .claude/hooks/block-generated-edits.sh PreToolUse hook enforces the generated-file
boundary; an attempted Edit/Write/MultiEdit on any file in the "DO NOT EDIT" row exits 2
with regen-flow guidance. The canonical glob list lives in
.claude/hooks/_lib/generated-paths.sh and is shared with format-on-edit.sh — adding
a new generated path means changing it once.
| Regenerate | Command | Time |
| Python client | uv run poe regenerate-client | ~1-2min |
| Vendored OpenAPI spec | uv run python scripts/vendor_spec.py | ~3s |
| API facts index | uv run poe facts | ~3s |
| Full pipeline (all three) | uv run poe regenerate-all | ~1-2min |
Commit Standards
feat(client): add feature # Client MINOR release
fix(mcp): fix bug # MCP PATCH release
docs: update README # No releaseUse ! for breaking changes: feat(client)!: breaking change
API Response Handling Best Practices
Use the helper utilities in frontapp_public_api_client/utils.py for consistent
response handling:
Response Unwrapping
from frontapp_public_api_client.utils import unwrap, unwrap_as, unwrap_data, is_success
from frontapp_public_api_client.domain.converters import unwrap_unset
from frontapp_public_api_client.models.conversation_response import ConversationResponse
# Single-object responses (200 OK with parsed model)
conv = unwrap_as(response, ConversationResponse) # Type-safe with validation
# Paginated list responses — Front wraps results in _results (→ field_results)
parsed = unwrap(response)
results = getattr(parsed, "field_results", None) or []
# For success-only responses (202 Accepted, 204 No Content)
if is_success(response):
# Handle success case
# For attrs model fields that may be UNSET
tags = unwrap_unset(conv.tags, []) # Returns [] if UNSETWhen to Use Each Pattern
| Scenario | Pattern | Example |
| Single object (200) | unwrap_as(response, Type) | Get/update by id |
| Paginated list (200) | unwrap(response) + getattr(…, "field_results") | Most list endpoints (see docs/api-facts.yaml summary block) |
| Raw-array list (200) | unwrap(response) (parsed is a list directly) | A handful of conversation sub-resource lists; see facts file |
| Create (201) | is_success(response) | POST with no body |
| Accepted (202) / Deleted (204) | is_success(response) | Replies, archives |
| attrs UNSET field | unwrap_unset(field, default) | Optional API fields |
For the authoritative per-endpoint shape (which list returns field_results vs a raw
array vs a single object), consult docs/api-facts.yaml —
summary.list_endpoints_with_field_results,
summary.list_endpoints_returning_raw_array, and
tags.<resource>.endpoints[].list_shape are CI-validated and cannot drift.
Anti-Patterns to Avoid
# ❌ DON'T: Manual status code checks
if response.status_code == 200:
result = response.parsed
# ✅ DO: Use helpers
result = unwrap_as(response, ExpectedType)
# ❌ DON'T: isinstance with UNSET
if not isinstance(value, type(UNSET)):
use(value)
# ✅ DO: Use unwrap_unset
use(unwrap_unset(value, default))
# ❌ DON'T: hasattr for attrs-defined fields
if hasattr(conv, "status"):
status = conv.status
# ✅ DO: Use unwrap_unset (attrs fields always exist, may be UNSET)
status = unwrap_unset(conv.status, None)Exception Hierarchy
unwrap() and unwrap_as() raise typed exceptions:
AuthenticationError- 401 UnauthorizedValidationError- 422 Unprocessable EntityRateLimitError- 429 Too Many RequestsServerError- 5xx server errorsAPIError- Other errors (400, 403, 404, etc.)
Claude Code Commands
Project slash commands in .claude/commands/ and skills in .claude/skills/:
| Command | Purpose |
/new-vertical | Scaffold a new resource vertical (helper + domain + MCP tools + tests + docs) |
/vendor-and-regen | Refresh the vendored Front OpenAPI spec from upstream and regenerate the client safely |
/review-pr | Address PR review comments — fetch, fix, validate, push, reply |
/open-pr | Open PR with self-review, CI wait, feedback |
/review | Structured code review of current branch |
/techdebt | Scan for tech debt and anti-patterns |
/write-tests | Write comprehensive tests for target code |
/generate-docs | Generate or update documentation |
/verify | Skeptically validate implementation quality |
/pre-commit | Quick pre-flight check before committing |
Claude Code Agents
Sub-agents in .claude/agents/ that can be spawned for delegated work during complex
tasks:
| Agent | Purpose |
vertical-planner | Plan a new resource vertical (helper + domain + MCP tools) BEFORE any code is written. Sonnet, Read+Bash+Grep+Glob. |
domain-advisor | Read-only oracle for Front API factual questions (response shape, module names, helper status). Haiku, Read+Grep+Glob (no Bash). |
code-modernizer | Simplify code using repo-specific patterns (UNSET helpers, response unwrap, etc.) |
pr-preparer | Validate branch readiness for pull request |
spec-auditor | Audit local OpenAPI spec against upstream for drift |
The vertical-planner and domain-advisor both consult docs/api-facts.yaml as their
primary knowledge source. When you have a one-off factual question about the API surface
mid-task, prefer domain-advisor over grep+ls.
Detailed Documentation
Discover on-demand - read these when working on specific areas:
| Topic | File |
| Agent workflows | AGENT_WORKFLOW.md |
| Client guide | frontapp_public_api_client/docs/guide.md |
| MCP docs | frontapp_mcp_server/docs/README.md |
| TypeScript client | packages/frontapp-client/README.md |
| ADRs | docs/adr/README.md |
Validation tiers, commit standards, and file rules are documented inline in this file — see "Essential Commands", "Commit Standards", and "File Rules" above.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

