From 917e08ba90e99102bcf8315b755d6095e63a1510 Mon Sep 17 00:00:00 2001 From: ErnestHysa Date: Sat, 30 May 2026 22:00:41 +0100 Subject: [PATCH 1/2] fix: validate settings schema before saving to prevent data loss --- src/utils/config.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/utils/config.ts b/src/utils/config.ts index 8f2ccc0c..4199b548 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -156,9 +156,16 @@ export async function loadSettings(): Promise { export async function saveSettings(settings: Settings): Promise { const paths = getSettingsPaths(); + // Validate settings before writing to prevent data loss + const parsed = SettingsSchema.safeParse(settings); + if (!parsed.success) { + console.error('Invalid settings format, not saving:', parsed.error); + return; + } + // Always include version when saving const settingsWithVersion = { - ...settings, + ...parsed.data, version: CURRENT_VERSION }; From 8443cfbb6fe4bfcc98d6e61d4b3eb7dc5779b8cb Mon Sep 17 00:00:00 2001 From: ErnestHysa Date: Sat, 30 May 2026 22:04:34 +0100 Subject: [PATCH 2/2] fix: use env override and settings for usable context ratio calculation --- src/types/Settings.ts | 5 +++- src/utils/model-context.ts | 61 +++++++++++++++++++++++++++++++++++--- 2 files changed, 61 insertions(+), 5 deletions(-) diff --git a/src/types/Settings.ts b/src/types/Settings.ts index efa2a31c..e74625c5 100644 --- a/src/types/Settings.ts +++ b/src/types/Settings.ts @@ -84,7 +84,10 @@ export const SettingsSchema = z.object({ message: z.string().nullable().optional(), remaining: z.number().nullable().optional() }).optional(), - installation: InstallationMetadataSchema.optional() + installation: InstallationMetadataSchema.optional(), + // Usable context ratio as a percentage (e.g., 80 = 80% = 0.8 ratio) + // Used by ContextPercentageUsable widget for auto-compact threshold calculation + autoCompactWindow: z.number().min(1).max(99).default(80) }); // Inferred type from schema diff --git a/src/utils/model-context.ts b/src/utils/model-context.ts index 74526e87..8e00e55d 100644 --- a/src/utils/model-context.ts +++ b/src/utils/model-context.ts @@ -9,7 +9,59 @@ interface ModelIdentifier { } const DEFAULT_CONTEXT_WINDOW_SIZE = 200000; -const USABLE_CONTEXT_RATIO = 0.8; +const DEFAULT_USABLE_CONTEXT_RATIO = 0.8; + +/** + * Gets the usable context ratio from environment variable or settings. + * Priority: CLAUDE_AUTOCOMPACT_PCT_OVERRIDE env var > autoCompactWindow setting > default 0.8 + * The ratio is expressed as a percentage (e.g., 80 = 80% = 0.8) + */ +function getUsableContextRatio(): number { + // First check environment variable override + const envValue = process.env.CLAUDE_AUTOCOMPACT_PCT_OVERRIDE; + if (envValue !== undefined) { + const parsed = Number.parseFloat(envValue); + if (Number.isFinite(parsed) && parsed > 0 && parsed <= 100) { + return parsed / 100; + } + } + + // Settings are loaded lazily to avoid circular dependencies + // The autoCompactWindow setting stores the percentage directly + try { + const settingsPath = require('path').join( + require('os').homedir(), + '.config', + 'ccstatusline', + 'settings.json' + ); + const fs = require('fs'); + if (fs.existsSync(settingsPath)) { + const content = fs.readFileSync(settingsPath, 'utf-8'); + const settings = JSON.parse(content); + if (settings.autoCompactWindow !== undefined) { + const parsed = Number.parseFloat(String(settings.autoCompactWindow)); + if (Number.isFinite(parsed) && parsed > 0 && parsed <= 100) { + return parsed / 100; + } + } + } + } catch { + // Ignore errors loading settings, fall back to default + } + + return DEFAULT_USABLE_CONTEXT_RATIO; +} + +// Cache the ratio so we don't re-read settings on every call +let cachedUsableContextRatio: number | null = null; + +function getCachedUsableContextRatio(): number { + if (cachedUsableContextRatio === null) { + cachedUsableContextRatio = getUsableContextRatio(); + } + return cachedUsableContextRatio; +} function toValidWindowSize(value: number | null | undefined): number | null { if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) { @@ -74,18 +126,19 @@ export function getModelContextIdentifier(model?: string | ModelIdentifier): str } export function getContextConfig(modelIdentifier?: string, contextWindowSize?: number | null): ModelContextConfig { + const usableRatio = getCachedUsableContextRatio(); const statusWindowSize = toValidWindowSize(contextWindowSize); if (statusWindowSize !== null) { return { maxTokens: statusWindowSize, - usableTokens: Math.floor(statusWindowSize * USABLE_CONTEXT_RATIO) + usableTokens: Math.floor(statusWindowSize * usableRatio) }; } // Default to 200k for older models const defaultConfig = { maxTokens: DEFAULT_CONTEXT_WINDOW_SIZE, - usableTokens: Math.floor(DEFAULT_CONTEXT_WINDOW_SIZE * USABLE_CONTEXT_RATIO) + usableTokens: Math.floor(DEFAULT_CONTEXT_WINDOW_SIZE * usableRatio) }; if (!modelIdentifier) { @@ -96,7 +149,7 @@ export function getContextConfig(modelIdentifier?: string, contextWindowSize?: n if (inferredWindowSize !== null) { return { maxTokens: inferredWindowSize, - usableTokens: Math.floor(inferredWindowSize * USABLE_CONTEXT_RATIO) + usableTokens: Math.floor(inferredWindowSize * usableRatio) }; }