Skip to content
claudewheel.profile_data
Edit
On this page

claudewheel's per-profile data store: the dot-prefixed directory inside each profile holding its OAuth token entry, that entry's format, and the owner-only modes both are written at.

#claudewheel.profile_data

#claudewheel.profile_data

claudewheel's own data, stored inside each profile directory.

A profile directory IS a Claude Code config dir: Claude Code owns everything in it, and claudewheel passes the path to it as CLAUDE_CONFIG_DIR. What claudewheel knows about a profile -- today the OAuth token and the plan-tier fields -- lives in one dot-prefixed subdirectory inside that same directory, :data:PROFILE_DATA_DIRNAME, so the profile and everything the launcher knows about it travel together: a rename moves the directory and the data with it, a deletion removes both, and nothing outside can be left pointing at a profile that no longer exists.

Layout, for a profile whose config dir is <profile_dir>::

/.claudewheel/ 0700, owner only /.claudewheel/token.json 0600, the token entry

The token entry is a single JSON object (never a name-keyed map -- the file already belongs to exactly one profile) in the shape :func:build_entry assembles:

================= ======================================================== token the OAuth token string created ISO date the entry was written expires_at ISO expiry date; present for a TTL disposition expiry_unknown true for an externally-issued token instead rateLimitTier declared plan tier, validated against Claude Code's set subscriptionType declared subscription type, validated the same way ================= ========================================================

Modes are set explicitly rather than inherited from the umask. The token file is written 0600 from creation by the secret writer, and the subdirectory is chmod'd 0700 every time it is ensured: a world-readable parent would expose the file's existence, its size and its mtime -- and on a directory whose mode was never stated, that is what the default gives.

#ProfileDataStore

Path-injected read/write facade over ONE profile's claudewheel data.

profile_dir is the profile's config dir (its CLAUDE_CONFIG_DIR); every path is derived from it and nothing here reads a module path constant or calls Path.home(). Construction is pure value assembly -- no directory is created until a write happens.

Read APIs raise :class:~claudewheel.tokens.TokenStoreError on a corrupt or unreadable token file; a missing file is not an error and reads as "this profile has no claudewheel data".

#data_dir

python
def data_dir(self) -> Path

The dot-prefixed subdirectory holding this profile's data.

#token_file

python
def token_file(self) -> Path

The token entry file inside :attr:data_dir.

#exists

python
def exists(self) -> bool

True when this profile carries a claudewheel data directory.

#load

python
def load(self) -> dict[str, Any]

Parse the token entry. Missing -> {}; corrupt -> TokenStoreError.

#_corrupt_message

python
def _corrupt_message(self, reason: object) -> str

The one wording for an unusable token file.

#token

python
def token(self) -> str | None

The token string, or None when there is none stored.

#has_token

python
def has_token(self) -> bool

True when a token string is stored for this profile.

#expiry

python
def expiry(self) -> TokenExpiry | None

Computed expiry of the stored entry, or None when there is no entry.

#tier

python
def tier(self) -> tuple[str | None, str | None]

The declared (rateLimitTier, subscriptionType), unvalidated.

The raw pair as stored, for reporting surfaces that show what is on disk rather than resolving it into launch environment variables (which validates -- see :meth:plan_env).

#declares_plan

python
def declares_plan(self) -> bool

True when this profile's entry declares a plan.

The pre-launch prompt's question: a profile launching on a stored token without one leaves Claude Code's tier null.

#plan_env

python
def plan_env(self) -> dict[str, str]

The declared plan tier as Claude Code env vars, validated.

An unrecognized value is a :class:ValueError naming the field, the file and the accepted values -- never a silently ignored field, because Claude Code treats a value it does not know exactly like no value.

#ensure_dir

python
def ensure_dir(self) -> None

Create :attr:data_dir if absent and set its mode explicitly.

#write_token

python
def write_token(self, token: str, *, expiry: TokenExpiryDisposition, plan: PlanTier, today: date | None=None) -> None

Write the token entry, replacing whatever was there.

expiry is required: the caller must choose how the token's lifetime is recorded (see :class:~claudewheel.tokens.TokenExpiryDisposition), so a lifetime is never silently fabricated.

plan is required too, and for the same reason -- no code path may put a token on disk without stating the plan it belongs to. Because the entry is rebuilt rather than merged into, replacing a profile's token invalidates the plan declared for the previous one: the caller states a plan again or writes nothing.

The directory is created at 0700 and the file written 0600 from creation.

#set_plan

python
def set_plan(self, plan: PlanTier) -> None

Merge plan's fields into the entry, creating it if absent.

The declaration path for a profile that already holds a token: the token, its dates and everything else in the entry are left alone. A corrupt entry file raises :class:~claudewheel.tokens.TokenStoreError rather than being overwritten.

#remove_token

python
def remove_token(self) -> bool

Delete the token entry file. True when it existed.

#PROFILE_DATA_DIRNAME

python
PROFILE_DATA_DIRNAME = '.claudewheel'

#PROFILE_DATA_DIR_MODE

python
PROFILE_DATA_DIR_MODE = 448

#TOKEN_FILE_NAME

python
TOKEN_FILE_NAME = 'token.json'

#TOKEN_FILE_MODE

python
TOKEN_FILE_MODE = 384

More tools from this site

  • claudestream Drive Claude Code from Python: run it as a subprocess and read its output as typed events, with async and sync sessions, sandbox policies, and tools you define in Python
  • dirstat Fast, single-binary directory statistics CLI: every file under a tree grouped by format, with counts, sizes, and lines of code, as a colored terminal table or as JSON
  • fastware A batteries-included ASGI framework: msgspec JSON, a managed Granian server, dependency injection, SSE, WebSockets, auth, and a test client
  • go-toml-edit Zero-dep TOML editing library for Go with comment preservation
  • howmuchleft The fastest Claude Code statusline: context window, 5-hour, and weekly limit usage as three customizable gradient bars, rendering in about 6 ms
  • orxtra
  • pgdesign
  • predraw Declarative rendering pipeline: describe a scene in JSON and get SVG, PNG and WebP out, with light and dark style tokens, reusable components and text converted to path outlines
  • reposummary Turn a git repository's history into a Markdown journal: pick a time window or revision range and get a readable digest of what changed, optionally narrated by an LLM
  • rlsbl Release orchestration and project scaffolding CLI that bumps versions, validates a structured JSONL changelog, tags only the commit CI verified, and publishes to npm, PyPI, Go and more
  • safegit git wrapper CLI that gives each commit its own temporary index and retries ref updates on conflict, so concurrent agents share one repository
  • saferm Command-line replacement for rm that archives every deletion with a mandatory reason and the context it ran in, so deleted files can be listed, inspected and restored
  • selfdoc Static Site Generator that builds a project's documentation site directly from its source code, so the docs can never drift from the code they describe, with SEO/AEO, first-class blog, search, and cross-project linking built in
  • strictcli
  • stricttest An always-on test-isolation floor: a pytest plugin and a Go env-hygiene module that make a test suite structurally unable to reach real credentials, the real HOME, the network, or the development repository.
  • wesktop A Python framework that turns an ASGI web app into a desktop application, serving it from a local Granian server and displaying it in a native OS window via pywebview
Search