claudewheel v0.20.1 /claudewheel.health
Edit
On this page

Pre-launch diagnostics: symlinks, tokens, disk usage, permission drift, and deployed hook-script drift checked against the canonical guardrail model.

#claudewheel.health

#claudewheel.health

Pre-launch diagnostics: symlinks, tokens, disk usage, and permission/hook drift against the canonical guardrail model.

#HealthResult

Health check result with ok status, label, and detail message.

#check_tmpfs_quota

python
def check_tmpfs_quota() -> HealthResult

Check /tmp usage percentage via df.

#_tmp_claude_dir

python
def _tmp_claude_dir() -> Path

Return the per-user Claude scratch dir under /tmp.

#_real_disk_usage

python
def _real_disk_usage(root: Path) -> int

Sum the real tmpfs block usage of regular files under root.

Correctness requirements this satisfies:

  • Never follows symlinks. os.walk(followlinks=False) does not descend into

symlinked directories, and lstat + S_ISREG skips symlinks to files. So symlink targets living outside /tmp (Claude session dirs link into home and project dirs) are never counted -- they consume zero /tmp space.

  • Counts REAL disk usage (st_blocks * 512), not apparent st_size. tmpfs

charges by allocated blocks; apparent size overcounts sparse files.

#check_tmp_claude_size

python
def check_tmp_claude_size() -> HealthResult

Check real tmpfs usage of /tmp/claude-$UID/ (excludes symlink targets).

#_discover_profiles

python
def _discover_profiles() -> list[ProfileInfo]

Find Claude profile dirs via the shared discovery module.

python
def check_shared_symlinks() -> HealthResult

Verify each profile's shared dirs are symlinks to ~/.claudewheel/shared/.

#_hook_wired

python
def _hook_wired(hooks: object, event: str, matcher: str, script: str) -> bool

Return True if hooks wires script under event with matcher.

An entry matches when its matcher equals matcher (an absent matcher is treated as the empty string, which is how UserPromptSubmit entries are stored) and it carries a hook command mentioning script.

#check_hooks_wired

python
def check_hooks_wired() -> HealthResult

Verify each profile wires every expected hook in settings.json.

The canonical wirings are the (event, matcher, script-name) triples in guardrail.EXPECTED_HOOK_WIRINGS. A profile passes only when every triple is present: an entry under the given event whose matcher equals the given matcher, containing a hook command that references the given script.

#check_settings_defaults

python
def check_settings_defaults() -> HealthResult

Verify each profile enforces expected defaults in settings.json.

#_diff_json

python
def _diff_json(label: str, canonical: object, actual: object) -> list[str]

Return human-readable lines describing differences between two JSON values.

#check_shared_settings_drift

python
def check_shared_settings_drift() -> HealthResult

Compare each profile's hooks and disallowedTools against shared-settings.json.

#_canonical_permission_diffs

python
def _canonical_permission_diffs(label: str, perms: object) -> list[str]

Return drift lines comparing a permissions block against the canonical model.

Checks permissions.deny and permissions.ask against guardrail.canonical_deny_rules() / canonical_ask_rules() (reporting missing canonical entries and extra non-canonical ones) and flags any permissions.allow entry that is a known dead/conflicting allow (guardrail.ALLOW_CONFLICTS).

#check_canonical_permissions_drift

python
def check_canonical_permissions_drift() -> HealthResult

Compare each profile's permissions against the canonical guardrail model.

For every profile settings.json and for shared-settings.json's profileDefaults (which seeds new profiles), verify that permissions.deny / permissions.ask match the canonical guardrail rules exactly and that no permissions.allow entry is a known dead/conflicting allow. Reports MISSING canonical entries, EXTRA non-canonical entries, and conflicting allows per profile. Warnings only -- never raises; ok is True only when everything matches and no conflicts exist.

#check_auth_shadow

python
def check_auth_shadow() -> HealthResult

Detect profiles where .credentials.json claudeAiOauth shadows a long-lived token.

#check_token_expiry

python
def check_token_expiry() -> HealthResult

Warn if any token is approaching 1-year expiry (setup-token TTL).

#check_tokens

python
def check_tokens() -> HealthResult

Verify each profile has a matching entry in ~/.claudewheel/tokens.json.

#check_orphan_profiles

python
def check_orphan_profiles() -> HealthResult

Detect profile dirs in ~/.claudewheel/profiles/ that are not registered.

A directory is "orphan" if it: - lives in ~/.claudewheel/profiles/ - is NOT discovered by _discover_profiles() (which checks .credentials.json, settings.json, and tokens.json) - is NOT listed in options.json's profile values

For each orphan, we also flag if it contains broken symlinks (symlinks whose target does not exist).

#check_file_permissions

python
def check_file_permissions() -> HealthResult

Verify sensitive files have restrictive permissions (0600).

#check_inode_renames

python
def check_inode_renames() -> HealthResult

Detect directory renames by comparing inode records against the filesystem.

#check_deployed_hook_drift

python
def check_deployed_hook_drift() -> HealthResult

Compare deployed hook scripts against the generated HOOK_SCRIPTS model.

Byte-hashes each script deployed under SCRIPTS_DIR against the corresponding HOOK_SCRIPTS[name] string (the canonical model, generated from the guardrail spec at import). Drift means a deployed script no longer matches what claudewheel deploy-hooks would write -- usually a stale copy left over after the model was regenerated.

Warn-only: reports drift but NEVER raises and is never a hard gate. Absence is not drift: if SCRIPTS_DIR does not exist (CI, fresh machines) or an individual model script has not been deployed yet, it is skipped and the check stays OK. Only the scripts present in both HOOK_SCRIPTS and on disk are compared.

#run_health_check

python
def run_health_check() -> list[HealthResult]

Run all health checks and return results.

python
def print_health_report(results: list[HealthResult], file=None) -> None

Print health check results. Defaults to stdout; pass file=sys.stderr for non-interactive mode.