Skip to content

Commit 282beaf

Browse files
ll0jj0xx0claudehappy-otter
committed
fix: apply Windows compatibility patches
- Set UTF-8 code page (chcp 65001) on Windows to fix garbled characters (PR slopus#912) - Handle Windows PID reuse in daemon state check with HTTP health ping (PR slopus#809) - Normalize paths to match Claude Code naming convention for special chars (PR slopus#392) Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
1 parent e749acc commit 282beaf

5 files changed

Lines changed: 56 additions & 6 deletions

File tree

packages/happy-app/sources/sync/gitStatusSync.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { parseStatusSummaryV2, getStatusCountsV2, isDirtyV2, getCurrentBranchV2,
1212
import { parseCurrentBranch } from './git-parsers/parseBranch';
1313
import { parseNumStat, mergeDiffSummaries } from './git-parsers/parseDiff';
1414
import { projectManager, createProjectKey } from './projectManager';
15+
import { normalizePathForKey } from '@/utils/normalizePathForKey';
1516

1617
export class GitStatusSync {
1718
// Map project keys to sync instances
@@ -20,14 +21,15 @@ export class GitStatusSync {
2021
private sessionToProjectKey = new Map<string, string>();
2122

2223
/**
23-
* Get project key string for a session
24+
* Get project key string for a session.
25+
* Uses normalized path to match Claude Code's .claude/projects folder naming convention.
2426
*/
2527
private getProjectKeyForSession(sessionId: string): string | null {
2628
const session = storage.getState().sessions[sessionId];
2729
if (!session?.metadata?.machineId || !session?.metadata?.path) {
2830
return null;
2931
}
30-
return `${session.metadata.machineId}:${session.metadata.path}`;
32+
return `${session.metadata.machineId}:${normalizePathForKey(session.metadata.path)}`;
3133
}
3234

3335
/**

packages/happy-app/sources/sync/projectManager.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*/
55

66
import { Session, MachineMetadata, GitStatus } from "./storageTypes";
7+
import { normalizePathForKey } from "@/utils/normalizePathForKey";
78

89
/**
910
* Unique project identifier based on machine ID and path
@@ -45,10 +46,11 @@ class ProjectManager {
4546
private nextProjectId = 1;
4647

4748
/**
48-
* Generate a unique key string from machine ID and path
49+
* Generate a unique key string from machine ID and path.
50+
* Uses normalized path to match Claude Code's .claude/projects folder naming convention.
4951
*/
5052
private getProjectKeyString(key: ProjectKey): string {
51-
return `${key.machineId}:${key.path}`;
53+
return `${key.machineId}:${normalizePathForKey(key.path)}`;
5254
}
5355

5456
/**
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/**
2+
* Normalizes a file path to match Claude Code's .claude/projects folder naming convention.
3+
*
4+
* Claude Code's actual algorithm (from @anthropic-ai/claude-code source):
5+
* 1. resolve(path) to get absolute path
6+
* 2. replace(/[^a-zA-Z0-9]/g, '-') -- every non-alphanumeric char becomes a hyphen
7+
* 3. if result > 200 chars, truncate to 200 and append a hash suffix
8+
*/
9+
export function normalizePathForKey(path: string): string {
10+
if (!path) {
11+
return '';
12+
}
13+
return path.replace(/[^a-zA-Z0-9]/g, '-');
14+
}

packages/happy-cli/scripts/claude_local_launcher.cjs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
const fs = require('fs');
22

3+
// Fix Windows Unicode rendering (box-drawing characters)
4+
if (process.platform === 'win32') {
5+
const { execSync } = require('child_process');
6+
try {
7+
execSync('chcp 65001', { stdio: 'ignore' });
8+
} catch (e) {
9+
// chcp not available, ignore
10+
}
11+
}
12+
313
// Disable autoupdater (never works really)
414
process.env.DISABLE_AUTOUPDATER = '1';
515

packages/happy-cli/src/daemon/controlClient.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,15 +121,37 @@ export async function checkIfDaemonRunningAndCleanupStaleState(): Promise<boolea
121121
return false;
122122
}
123123

124-
// Check if the daemon is running
124+
// Check if the PID is alive
125125
try {
126126
process.kill(state.pid, 0);
127-
return true;
128127
} catch {
129128
logger.debug('[DAEMON RUN] Daemon PID not running, cleaning up state');
130129
await cleanupDaemonState();
131130
return false;
132131
}
132+
133+
// PID is alive, but on Windows PIDs get reused after reboot.
134+
// Verify it's actually our daemon by HTTP pinging its control server.
135+
if (state.httpPort) {
136+
try {
137+
const response = await fetch(`http://127.0.0.1:${state.httpPort}/list`, {
138+
method: 'POST',
139+
headers: { 'Content-Type': 'application/json' },
140+
body: '{}',
141+
signal: AbortSignal.timeout(2000)
142+
});
143+
if (response.ok) {
144+
return true;
145+
}
146+
} catch {
147+
// HTTP check failed - the PID is not our daemon (likely reused by OS after reboot)
148+
logger.debug(`[DAEMON RUN] PID ${state.pid} is alive but HTTP health check failed on port ${state.httpPort}, cleaning up stale state`);
149+
await cleanupDaemonState();
150+
return false;
151+
}
152+
}
153+
154+
return true;
133155
}
134156

135157
/**

0 commit comments

Comments
 (0)