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
def check_tmpfs_quota() -> HealthResultCheck /tmp usage percentage via df.
#_tmp_claude_dir
def _tmp_claude_dir() -> PathReturn the per-user Claude scratch dir under /tmp.
#_real_disk_usage
def _real_disk_usage(root: Path) -> intSum 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
def check_tmp_claude_size() -> HealthResultCheck real tmpfs usage of /tmp/claude-$UID/ (excludes symlink targets).
#_discover_profiles
def _discover_profiles() -> list[ProfileInfo]Find Claude profile dirs via the shared discovery module.
#check_shared_symlinks
def check_shared_symlinks() -> HealthResultVerify each profile's shared dirs are symlinks to ~/.claudewheel/shared/.
#_hook_wired
def _hook_wired(hooks: object, event: str, matcher: str, script: str) -> boolReturn 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
def check_hooks_wired() -> HealthResultVerify 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
def check_settings_defaults() -> HealthResultVerify each profile enforces expected defaults in settings.json.
#_diff_json
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
def check_shared_settings_drift() -> HealthResultCompare each profile's hooks and disallowedTools against shared-settings.json.
#_canonical_permission_diffs
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
def check_canonical_permissions_drift() -> HealthResultCompare 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
def check_auth_shadow() -> HealthResultDetect profiles where .credentials.json claudeAiOauth shadows a long-lived token.
#check_token_expiry
def check_token_expiry() -> HealthResultWarn if any token is approaching 1-year expiry (setup-token TTL).
#check_tokens
def check_tokens() -> HealthResultVerify each profile has a matching entry in ~/.claudewheel/tokens.json.
#check_orphan_profiles
def check_orphan_profiles() -> HealthResultDetect 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
def check_file_permissions() -> HealthResultVerify sensitive files have restrictive permissions (0600).
#check_inode_renames
def check_inode_renames() -> HealthResultDetect directory renames by comparing inode records against the filesystem.
#check_deployed_hook_drift
def check_deployed_hook_drift() -> HealthResultCompare 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
def run_health_check() -> list[HealthResult]Run all health checks and return results.
#print_health_report
def print_health_report(results: list[HealthResult], file=None) -> NonePrint health check results. Defaults to stdout; pass file=sys.stderr for non-interactive mode.