fix: address PR #373 review feedback

- postuninstall.js: add file logging for debugging on error
- profile-hook-injector.ts: use 'wx' flag for atomic marker creation
- profile-hook-injector.ts: include parse error message in debug log
- install-command.ts: use actual counts for consistent semantics
- Windows tests: align Section 7 with per-profile hook architecture
This commit is contained in:
kaitranntt
2026-01-25 22:02:25 -05:00
parent e0891fe915
commit e98a92fded
4 changed files with 43 additions and 51 deletions
+8 -2
View File
@@ -34,8 +34,14 @@ function cleanupCcsFiles() {
// Note: Do NOT touch ~/.claude/settings.json
// Per-profile hooks in ~/.ccs/*.settings.json will be cleaned up
// when the user removes ~/.ccs/ directory.
} catch {
// Silent fail - not critical
} catch (err) {
// Silent fail - not critical, but log for debugging
try {
const logPath = path.join(CCS_DIR, 'uninstall.log');
fs.appendFileSync(logPath, `${new Date().toISOString()}: ${err.message}\n`);
} catch {
// Ignore logging failures
}
}
}
+2 -4
View File
@@ -39,15 +39,13 @@ export async function handleUninstallCommand(): Promise<void> {
const hookRemoved = uninstallWebSearchHook();
if (hookRemoved) {
console.log(ok('Removed WebSearch hook'));
removed++;
removed += 1; // Count as 1 item (the hook file)
}
// 2. Remove symlinks from ~/.claude/
const symlinkManager = new ClaudeSymlinkManager();
const symlinksRemoved = symlinkManager.uninstall();
if (symlinksRemoved > 0) {
removed++;
}
removed += symlinksRemoved; // Add actual count of symlinks removed
// 3. Summary
console.log('');
+6 -4
View File
@@ -63,8 +63,8 @@ function migrateGlobalHook(): void {
if (!fs.existsSync(ccsDir)) {
fs.mkdirSync(ccsDir, { recursive: true, mode: 0o700 });
}
// Create marker file
fs.writeFileSync(markerPath, new Date().toISOString(), 'utf8');
// Create marker file atomically (wx = fail if exists, prevents race condition)
fs.writeFileSync(markerPath, new Date().toISOString(), { encoding: 'utf8', flag: 'wx' });
} catch (error) {
if (process.env.CCS_DEBUG) {
console.error(warn(`Migration failed: ${(error as Error).message}`));
@@ -114,9 +114,11 @@ export function ensureProfileHooks(profileName: string): boolean {
try {
const content = fs.readFileSync(settingsPath, 'utf8');
settings = JSON.parse(content);
} catch {
} catch (parseError) {
if (process.env.CCS_DEBUG) {
console.error(warn(`Malformed ${profileName}.settings.json - creating fresh hooks`));
console.error(
warn(`Malformed ${profileName}.settings.json: ${(parseError as Error).message}`)
);
}
// Continue with empty settings, will add hooks
}
+27 -41
View File
@@ -428,42 +428,15 @@ Test-Case "Edge case: Missing parent directories" "Handles missing .claude direc
}
# ============================================================================
# TEST SECTION 7: SETTINGS.JSON HOOK CLEANUP
# TEST SECTION 7: PER-PROFILE HOOK BEHAVIOR (Global settings untouched)
# ============================================================================
Write-Host ""
Write-ColorOutput "========================================" "Yellow"
Write-ColorOutput "SECTION 7: SETTINGS.JSON HOOK CLEANUP" "Yellow"
Write-ColorOutput "SECTION 7: PER-PROFILE HOOK BEHAVIOR" "Yellow"
Write-ColorOutput "========================================" "Yellow"
Test-Case "Hook cleanup: Removes CCS WebSearch hook from settings.json" "Hook removed" {
# Setup: Create settings.json with CCS hook
New-Item -ItemType Directory -Path $TestClaudeDir -Force | Out-Null
$settingsPath = Join-Path $TestClaudeDir "settings.json"
$settingsContent = @'
{
"hooks": {
"PreToolUse": [
{ "matcher": "WebSearch", "hooks": [{ "command": "node ~/.ccs/hooks/websearch-transformer.cjs" }] }
]
}
}
'@
Set-Content -Path $settingsPath -Value $settingsContent
# Install then uninstall
$env:HOME = $TestHome
try {
& $CcsPath --install | Out-Null
& $CcsPath --uninstall | Out-Null
} catch { }
# Check hook removed
$content = Get-Content -Path $settingsPath -Raw
-not ($content -match "websearch-transformer")
}
Test-Case "Hook cleanup: Preserves user-defined WebSearch hooks" "User hooks kept" {
Test-Case "Per-profile: Uninstall does NOT touch global settings.json" "Global settings unchanged" {
# Setup: Create settings.json with user hook
New-Item -ItemType Directory -Path $TestClaudeDir -Force | Out-Null
$settingsPath = Join-Path $TestClaudeDir "settings.json"
@@ -484,13 +457,13 @@ Test-Case "Hook cleanup: Preserves user-defined WebSearch hooks" "User hooks kep
& $CcsPath --uninstall | Out-Null
} catch { }
# Check user hook preserved
# Verify global settings unchanged
$content = Get-Content -Path $settingsPath -Raw
$content -match "my-custom-hook"
}
Test-Case "Hook cleanup: Handles mixed CCS and user hooks" "Only CCS hook removed" {
# Setup: Create settings.json with both CCS and user hooks
Test-Case "Per-profile: Uninstall preserves CCS hooks in global settings (not touched)" "Both hooks preserved" {
# Setup: Create settings.json with CCS hook (old format - shouldn't be touched)
New-Item -ItemType Directory -Path $TestClaudeDir -Force | Out-Null
$settingsPath = Join-Path $TestClaudeDir "settings.json"
$settingsContent = @'
@@ -498,28 +471,41 @@ Test-Case "Hook cleanup: Handles mixed CCS and user hooks" "Only CCS hook remove
"hooks": {
"PreToolUse": [
{ "matcher": "WebSearch", "hooks": [{ "command": "node ~/.ccs/hooks/websearch-transformer.cjs" }] },
{ "matcher": "WebSearch", "hooks": [{ "command": "my-custom-hook.sh" }] },
{ "matcher": "Bash", "hooks": [{ "command": "bash-hook.sh" }] }
{ "matcher": "WebSearch", "hooks": [{ "command": "my-custom-hook.sh" }] }
]
}
}
'@
Set-Content -Path $settingsPath -Value $settingsContent
# Uninstall - should NOT modify global settings
$env:HOME = $TestHome
try {
& $CcsPath --uninstall | Out-Null
} catch { }
# Both hooks should still be in global settings (not touched)
$content = Get-Content -Path $settingsPath -Raw
($content -match "websearch-transformer") -and ($content -match "my-custom-hook")
}
Test-Case "Per-profile: Migration marker cleanup on uninstall" "Marker file removed" {
# Setup: Create ~/.ccs/.hook-migrated marker
$ccsDir = Join-Path $TestHome ".ccs"
New-Item -ItemType Directory -Path $ccsDir -Force | Out-Null
Set-Content -Path (Join-Path $ccsDir ".hook-migrated") -Value "2026-01-25T00:00:00.000Z"
# Uninstall
$env:HOME = $TestHome
try {
& $CcsPath --uninstall | Out-Null
} catch { }
# Check CCS hook removed, user hooks preserved
$content = Get-Content -Path $settingsPath -Raw
(-not ($content -match "websearch-transformer")) -and
($content -match "my-custom-hook") -and
($content -match "bash-hook")
# Verify marker removed
-not (Test-Path (Join-Path $ccsDir ".hook-migrated"))
}
Test-Case "Hook cleanup: Handles missing settings.json" "No error when settings.json missing" {
Test-Case "Per-profile: Handles missing settings.json" "No error when settings.json missing" {
# Ensure settings.json doesn't exist
$settingsPath = Join-Path $TestClaudeDir "settings.json"
if (Test-Path $settingsPath) {