Skip to content

Commit 4a0ff0b

Browse files
FelixIsaacclaude
andcommitted
fix: pattern matching for exclusions/encryption
- Fix PowerShell wildcard detection using .Contains('*') instead of broken backtick escaping - Fix stderr redirection for git commands (2>&1 instead of 2>$null) - Add missing encryption patterns (.credentials.json, client_secret_*.json) - Add missing exclusion patterns (debug/, file-history/, ide/, plugins/, etc.) - Bump version to 0.1.1 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent d93188e commit 4a0ff0b

3 files changed

Lines changed: 108 additions & 34 deletions

File tree

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ claude-code-sync pull
120120
- `settings.json` - May contain API keys in `env`
121121
- `settings.local.json` - User overrides
122122
- `~/.claude.json` - OAuth tokens, MCP server configs (separate file at home dir, not inside ~/.claude/)
123+
- `.credentials.json` - OAuth credentials
124+
- `client_secret_*.json` - OAuth client secrets
123125
- `skills/*/resources/*` - May contain credentials
124126

125127
### Excluded (not synced)
@@ -129,6 +131,13 @@ claude-code-sync pull
129131
- `statsig/` - Analytics/telemetry data
130132
- `history.jsonl` - Conversation history index
131133
- `todos/` - Session-specific todo files
134+
- `debug/` - Debug logs
135+
- `file-history/` - File history cache
136+
- `ide/` - IDE integration cache
137+
- `plugins/` - Plugin cache
138+
- `shell-snapshots/` - Shell state snapshots
139+
- `telemetry/` - Telemetry data
140+
- `stats-cache.json` - Statistics cache
132141
- `*.log`, `*.tmp`, `*.cache`
133142

134143
## Security

claude-code-sync

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# https://github.com/felixisaac/claude-code-sync
44
set -euo pipefail
55

6-
VERSION="0.1.0"
6+
VERSION="0.1.1"
77

88
# Directories
99
CLAUDE_DIR="$HOME/.claude"
@@ -27,23 +27,34 @@ ENCRYPT_PATTERNS=(
2727
"settings.json"
2828
"settings.local.json"
2929
"claude.json"
30+
".credentials.json"
31+
"client_secret_*.json"
3032
"skills/*/resources/*"
3133
)
3234

3335
# Files/dirs to exclude from sync
3436
EXCLUDE_PATTERNS=(
37+
# Directories
3538
"plans"
36-
"*.log"
37-
"*.tmp"
38-
".git"
39-
"*.local-backup-*"
40-
"sessionStorage"
41-
"*.cache"
4239
"projects"
4340
"local"
4441
"statsig"
45-
"history.jsonl"
4642
"todos"
43+
"debug"
44+
"file-history"
45+
"ide"
46+
"plugins"
47+
"shell-snapshots"
48+
"telemetry"
49+
"sessionStorage"
50+
# Files
51+
"history.jsonl"
52+
"stats-cache.json"
53+
"*.log"
54+
"*.tmp"
55+
"*.cache"
56+
"*.local-backup-*"
57+
".git"
4758
)
4859

4960
#--- Utility Functions ---#
@@ -89,16 +100,26 @@ get_public_key() {
89100
should_encrypt() {
90101
local file="$1"
91102
local relpath="${file#$CLAUDE_DIR/}"
103+
local filename
104+
filename=$(basename "$relpath")
92105

93106
# Check against encrypt patterns
94107
for pattern in "${ENCRYPT_PATTERNS[@]}"; do
95108
# shellcheck disable=SC2053
96-
if [[ "$relpath" == $pattern ]] || [[ "$(basename "$relpath")" == "$pattern" ]]; then
97-
return 0
109+
if [[ "$pattern" == *"*"* ]]; then
110+
# Wildcard pattern - match against filename or full path
111+
if [[ "$filename" == $pattern ]] || [[ "$relpath" == $pattern ]]; then
112+
return 0
113+
fi
114+
else
115+
# Exact match against filename
116+
if [[ "$filename" == "$pattern" ]]; then
117+
return 0
118+
fi
98119
fi
99120
done
100121

101-
# Also encrypt claude.json (separate file)
122+
# Also encrypt claude.json (separate file at ~/)
102123
if [[ "$file" == "$CLAUDE_JSON" ]]; then
103124
return 0
104125
fi
@@ -109,11 +130,25 @@ should_encrypt() {
109130
should_exclude() {
110131
local file="$1"
111132
local relpath="${file#$CLAUDE_DIR/}"
133+
local filename
134+
filename=$(basename "$relpath")
112135

113136
for pattern in "${EXCLUDE_PATTERNS[@]}"; do
114137
# shellcheck disable=SC2053
115-
if [[ "$relpath" == $pattern* ]] || [[ "$(basename "$relpath")" == $pattern ]]; then
116-
return 0
138+
if [[ "$pattern" == *"*"* ]]; then
139+
# Wildcard pattern - match against filename
140+
if [[ "$filename" == $pattern ]]; then
141+
return 0
142+
fi
143+
else
144+
# Directory/file name - match if relpath starts with pattern/ or equals pattern
145+
if [[ "$relpath" == "$pattern" ]] || [[ "$relpath" == "$pattern/"* ]]; then
146+
return 0
147+
fi
148+
# Also check exact filename match
149+
if [[ "$filename" == "$pattern" ]]; then
150+
return 0
151+
fi
117152
fi
118153
done
119154
return 1

claude-code-sync.ps1

Lines changed: 51 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ param(
1818
)
1919

2020
$ErrorActionPreference = "Stop"
21-
$VERSION = "0.1.0"
21+
$VERSION = "0.1.1"
2222

2323
# Directories
2424
$CLAUDE_DIR = Join-Path $env:USERPROFILE ".claude"
@@ -30,26 +30,38 @@ $REPO_DIR = Join-Path $SYNC_DIR "repo"
3030
$BACKUP_DIR = Join-Path $SYNC_DIR "backups"
3131
$LOCK_FILE = Join-Path $SYNC_DIR ".lock"
3232

33-
# Patterns
33+
# Patterns - files to encrypt (contain secrets)
3434
$ENCRYPT_PATTERNS = @(
3535
"settings.json",
3636
"settings.local.json",
37-
"claude.json"
37+
"claude.json",
38+
".credentials.json",
39+
"client_secret_*.json"
3840
)
3941

42+
# Patterns - files/folders to exclude (not synced)
4043
$EXCLUDE_PATTERNS = @(
44+
# Directories
4145
"plans",
42-
"*.log",
43-
"*.tmp",
44-
".git",
45-
"*.local-backup-*",
46-
"sessionStorage",
47-
"*.cache",
4846
"projects",
4947
"local",
5048
"statsig",
49+
"todos",
50+
"debug",
51+
"file-history",
52+
"ide",
53+
"plugins",
54+
"shell-snapshots",
55+
"telemetry",
56+
"sessionStorage",
57+
# Files
5158
"history.jsonl",
52-
"todos"
59+
"stats-cache.json",
60+
"*.log",
61+
"*.tmp",
62+
"*.cache",
63+
"*.local-backup-*",
64+
".git"
5365
)
5466

5567
#--- Utility Functions ---#
@@ -95,17 +107,20 @@ function Test-ShouldEncrypt {
95107
$relPath = $FilePath.Replace("$CLAUDE_DIR\", "").Replace("$CLAUDE_DIR/", "")
96108

97109
foreach ($pattern in $ENCRYPT_PATTERNS) {
98-
if ($fileName -eq $pattern -or $relPath -like $pattern) {
99-
return $true
110+
# Use wildcard matching for patterns with *, exact match otherwise
111+
if ($pattern.Contains('*')) {
112+
if ($fileName -like $pattern) { return $true }
113+
} else {
114+
if ($fileName -eq $pattern) { return $true }
100115
}
101116
}
102117

103-
# Also encrypt claude.json
118+
# Also encrypt claude.json (separate file at ~/)
104119
if ($FilePath -eq $CLAUDE_JSON) {
105120
return $true
106121
}
107122

108-
# Encrypt resources in skills
123+
# Encrypt resources in skills (may contain credentials)
109124
if ($relPath -like "skills\*\resources\*" -or $relPath -like "skills/*/resources/*") {
110125
return $true
111126
}
@@ -118,10 +133,25 @@ function Test-ShouldExclude {
118133

119134
$fileName = Split-Path $FilePath -Leaf
120135
$relPath = $FilePath.Replace("$CLAUDE_DIR\", "").Replace("$CLAUDE_DIR/", "")
136+
# Normalize to forward slashes for consistent matching (case-insensitive)
137+
$relPathNorm = $relPath.Replace("\", "/").ToLower()
121138

122139
foreach ($pattern in $EXCLUDE_PATTERNS) {
123-
if ($relPath -like "$pattern*" -or $fileName -like $pattern) {
124-
return $true
140+
$patternLower = $pattern.ToLower()
141+
if ($pattern.Contains('*')) {
142+
# Wildcard pattern - match against filename
143+
if ($fileName -like $pattern) {
144+
return $true
145+
}
146+
} else {
147+
# Directory/file name - match if relPath starts with pattern/ or equals pattern
148+
if ($relPathNorm -eq $patternLower -or $relPathNorm.StartsWith("$patternLower/")) {
149+
return $true
150+
}
151+
# Also check exact filename match for file patterns like "history.jsonl"
152+
if ($fileName.ToLower() -eq $patternLower) {
153+
return $true
154+
}
125155
}
126156
}
127157
return $false
@@ -359,7 +389,7 @@ function Invoke-Pull {
359389
# Backup existing file if different
360390
if (Test-Path $dest) {
361391
$existingContent = Get-Content $dest -Raw -ErrorAction SilentlyContinue
362-
$newContent = & age -d -i $KEY_FILE $file 2>$null
392+
$newContent = & age -d -i $KEY_FILE $file 2>&1
363393
if ($existingContent -ne $newContent) {
364394
$backupName = "$dest.local-backup-$(Get-Timestamp)"
365395
Write-Warn "Conflict: backing up $actualRelPath to $backupName"
@@ -408,9 +438,9 @@ function Invoke-Status {
408438
# Check remote status
409439
$remotes = & git -C $REPO_DIR remote
410440
if ($remotes -contains "origin") {
411-
& git -C $REPO_DIR fetch origin 2>$null
412-
$localCommit = & git -C $REPO_DIR rev-parse HEAD 2>$null
413-
$remoteCommit = & git -C $REPO_DIR rev-parse origin/HEAD 2>$null
441+
$null = & git -C $REPO_DIR fetch origin 2>&1
442+
$localCommit = & git -C $REPO_DIR rev-parse HEAD 2>&1 | Select-Object -First 1
443+
$remoteCommit = & git -C $REPO_DIR rev-parse origin/HEAD 2>&1 | Select-Object -First 1
414444

415445
if ($localCommit -eq $remoteCommit) {
416446
Write-Host "Remote: " -NoNewline; Write-Host "Up to date" -ForegroundColor Green
@@ -604,7 +634,7 @@ function Invoke-Unlink {
604634
return
605635
}
606636

607-
$remotes = & git -C $REPO_DIR remote 2>$null
637+
$remotes = & git -C $REPO_DIR remote 2>&1
608638
if ($remotes -contains "origin") {
609639
& git -C $REPO_DIR remote remove origin
610640
if (Test-Path $CONFIG_FILE) { Remove-Item -Force $CONFIG_FILE }

0 commit comments

Comments
 (0)