Skip to content

Commit 77fd87f

Browse files
vscarpenterclaude
andauthored
fix: resolve critical and major issues from code review (#145)
* fix: resolve critical and major issues from code review - Add dryRun support to bulkUpdateTasks (was silently mutating data) - Separate deleted vs updated counts in bulk operation responses - Fix fallback totalTasks using nullable instead of misleading pending counts - Thread request Origin through auth middleware for correct CORS on errors - Add environment parameter to CORS response helpers for dev-port support - Optimize list-tasks cache to serve filtered queries from cached all-tasks - Initialize encryption once per batch instead of per-task in decryption loop - Fix import ordering in list-tasks.ts - Bump version to 6.8.8 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address minor code review issues across frontend, MCP server, and worker Frontend: - Use structured logObject for all log levels (not just error) in logger.ts - Add 'email' to sensitive-key sanitization list for PII protection - Fix import ordering in oauth-handshake/subscriber.ts - Wrap error string in Error object in oauth-callback-handler.tsx - Fix misleading "localStorage" log message (writes to sessionStorage) - Fix indentation in pwa-register.tsx - Clean up controllerchange listener and fallback timer in pwa-update-toast.tsx MCP Server: - Gate debug logs behind LOG_LEVEL env var in MCP logger - Fix help text: 18 → 20 total tools, add missing tool listings - Remove unsafe type cast for statsReset; use properly typed result object - Fix Math.max(...spread) stack overflow risk; use reduce for large arrays - Downgrade config guidance strings from ERROR to INFO level - Add error context to sync-status catch for better diagnostics Worker: - Replace positional ALLOWED_ORIGINS[0]/[1] with named constants - Fix double encodeURIComponent in buildErrorRedirect - Add runtime type coercion for D1 query results (fixes pre-existing TS error) - Downgrade pull processing log from INFO to DEBUG, remove vector clock noise - Clean up informal "FIX #N" and "BULLETPROOF FIX" comment labels Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent a58384b commit 77fd87f

File tree

22 files changed

+144
-97
lines changed

22 files changed

+144
-97
lines changed

components/oauth-callback-handler.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,9 +175,8 @@ export function OAuthCallbackHandler() {
175175
if (event.status === 'success') {
176176
await processAuthData(event.authData, event.state);
177177
} else {
178-
logger.error('OAuth handshake error', undefined, {
178+
logger.error('OAuth handshake error', new Error(event.error ?? 'Unknown OAuth error'), {
179179
state: event.state.substring(0, 8) + '...',
180-
error: event.error,
181180
});
182181
toast.error(event.error || 'Sign in failed. Please try again.');
183182
}

components/pwa-register.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export function PwaRegister() {
4444
navigator.serviceWorker.controller
4545
) {
4646
// New service worker is ready
47-
logger.info('New service worker available');
47+
logger.info('New service worker available');
4848

4949
// Dispatch custom event to notify PwaUpdateToast
5050
window.dispatchEvent(

components/pwa-update-toast.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ export function PwaUpdateToast() {
5252
// Listen for the service worker to actually take over
5353
// IMPORTANT: Attach listener BEFORE posting message to avoid race condition
5454
const controllerChangeHandler = () => {
55+
clearTimeout(fallbackTimeout);
56+
navigator.serviceWorker.removeEventListener("controllerchange", controllerChangeHandler);
5557
logger.info('Service worker controller changed');
5658
performReload();
5759
};
@@ -61,6 +63,7 @@ export function PwaUpdateToast() {
6163
// Fallback: If controllerchange doesn't fire within 2 seconds (iOS/Safari issue),
6264
// reload anyway to ensure the user gets the update
6365
const fallbackTimeout = setTimeout(() => {
66+
navigator.serviceWorker.removeEventListener("controllerchange", controllerChangeHandler);
6467
logger.warn('Fallback reload triggered (controllerchange not fired)');
6568
performReload();
6669
}, 2000);
@@ -71,8 +74,9 @@ export function PwaUpdateToast() {
7174
waitingWorker.postMessage({ type: "SKIP_WAITING" });
7275
} catch (error) {
7376
logger.error('Failed to post SKIP_WAITING message', error instanceof Error ? error : new Error(String(error)));
74-
// If posting the message fails, clear timeout and just reload
77+
// If posting the message fails, clean up and just reload
7578
clearTimeout(fallbackTimeout);
79+
navigator.serviceWorker.removeEventListener("controllerchange", controllerChangeHandler);
7680
performReload();
7781
}
7882
};

lib/logger.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ function sanitizeMetadata(metadata?: LogMetadata): LogMetadata | undefined {
100100
}
101101

102102
// Remove sensitive fields
103-
const sensitiveKeys = ['token', 'password', 'secret', 'apiKey', 'authorization', 'passphrase'];
103+
const sensitiveKeys = ['token', 'password', 'secret', 'apiKey', 'authorization', 'passphrase', 'email'];
104104
for (const key of sensitiveKeys) {
105105
if (key in sanitized) {
106106
sanitized[key] = '***';
@@ -130,16 +130,16 @@ function formatLog(
130130
...(sanitized && Object.keys(sanitized).length > 0 ? { metadata: sanitized } : {}),
131131
};
132132

133-
// Use appropriate console method
133+
// Use structured logObject for all levels for consistent output
134134
switch (level) {
135135
case 'debug':
136-
console.debug(`[${context}]`, message, sanitized || '');
136+
console.debug(`[${context}]`, logObject);
137137
break;
138138
case 'info':
139-
console.log(`[${context}]`, message, sanitized || '');
139+
console.log(`[${context}]`, logObject);
140140
break;
141141
case 'warn':
142-
console.warn(`[${context}]`, message, sanitized || '');
142+
console.warn(`[${context}]`, logObject);
143143
break;
144144
case 'error':
145145
console.error(`[${context}]`, logObject);

lib/sync/oauth-handshake/broadcaster.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,6 @@ function storePayload(payload: BroadcastPayload): void {
108108
try {
109109
storage?.setItem(STORAGE_KEY, JSON.stringify(payload));
110110
} catch (err) {
111-
logger.warn('Failed to write localStorage payload', { error: String(err) });
111+
logger.warn('Failed to write sessionStorage payload', { error: String(err) });
112112
}
113113
}

lib/sync/oauth-handshake/subscriber.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ import { toast } from 'sonner';
66
import type { OAuthHandshakeEvent } from './types';
77
import { listeners, processedStates } from './state';
88
import { ensureInitialized } from './initializer';
9+
import { initiateHandshakeFetch } from './fetcher';
910
import { createLogger } from '@/lib/logger';
1011

1112
const logger = createLogger('OAUTH');
12-
import { initiateHandshakeFetch } from './fetcher';
1313

1414
/**
1515
* Subscribe to OAuth handshake events

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "gsd-taskmanager",
3-
"version": "6.8.7",
3+
"version": "6.8.8",
44
"private": true,
55
"scripts": {
66
"dev": "next dev",

packages/mcp-server/src/server/config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ export function loadConfig(): GsdConfig {
2929
});
3030
} catch (error) {
3131
logger.error('Configuration error', error instanceof Error ? error : new Error(String(error)));
32-
logger.error('Required environment variables: GSD_API_URL, GSD_AUTH_TOKEN | Optional: GSD_ENCRYPTION_PASSPHRASE');
33-
logger.error('Run setup wizard with: npx gsd-mcp-server --setup');
32+
logger.info('Required environment variables: GSD_API_URL, GSD_AUTH_TOKEN | Optional: GSD_ENCRYPTION_PASSPHRASE');
33+
logger.info('Run setup wizard with: npx gsd-mcp-server --setup');
3434
throw error;
3535
}
3636
}

packages/mcp-server/src/tools/handlers/system-handlers.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ export async function handleValidateConfig(config: GsdConfig): Promise<McpToolRe
9595
function buildToolsHelpSection(): string {
9696
return `# GSD Task Manager MCP Server - Help
9797
98-
## Available Tools (18 total)
98+
## Available Tools (20 total)
9999
100100
### Metadata & Status Tools
101101
- **get_sync_status** - Check sync health, device count, storage usage
@@ -121,9 +121,11 @@ function buildToolsHelpSection(): string {
121121
- **delete_task** - Permanently delete a task
122122
- **bulk_update_tasks** - Update multiple tasks at once (max 50)
123123
124-
### Configuration Tools
124+
### Configuration & System Tools
125125
- **validate_config** - Diagnose configuration issues
126126
- **get_help** - This help message (supports topic filtering)
127+
- **get_cache_stats** - View cache performance statistics
128+
- **get_token_status** - Check auth token expiration status
127129
128130
`;
129131
}
@@ -321,7 +323,13 @@ export async function handleGetCacheStats(args: { reset?: boolean }): Promise<Mc
321323
const cache = getTaskCache();
322324
const stats = cache.getStats();
323325

324-
const result = {
326+
const result: {
327+
performance: { hitRate: string; hits: number; misses: number };
328+
taskListCache: { currentSize: number; maxEntries: number; ttlSeconds: number };
329+
singleTaskCache: { currentSize: number; maxEntries: number; ttlSeconds: number };
330+
notes: string[];
331+
statsReset?: boolean;
332+
} = {
325333
performance: {
326334
hitRate: `${(stats.hitRate * 100).toFixed(1)}%`,
327335
hits: stats.hits,
@@ -347,7 +355,7 @@ export async function handleGetCacheStats(args: { reset?: boolean }): Promise<Mc
347355
// Reset stats if requested
348356
if (args.reset) {
349357
cache.resetStats();
350-
(result as { statsReset?: boolean }).statsReset = true;
358+
result.statsReset = true;
351359
}
352360

353361
return {

packages/mcp-server/src/tools/handlers/write-handlers.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -159,12 +159,25 @@ export async function handleDeleteTask(config: GsdConfig, args: { id: string; dr
159159

160160
export async function handleBulkUpdateTasks(
161161
config: GsdConfig,
162-
args: { taskIds: string[]; operation: BulkOperation; maxTasks?: number }
162+
args: { taskIds: string[]; operation: BulkOperation; maxTasks?: number; dryRun?: boolean }
163163
): Promise<McpToolResponse> {
164-
const result = await bulkUpdateTasks(config, args.taskIds, args.operation, { maxTasks: args.maxTasks });
164+
const result = await bulkUpdateTasks(config, args.taskIds, args.operation, {
165+
maxTasks: args.maxTasks,
166+
dryRun: args.dryRun,
167+
});
168+
169+
let message: string;
170+
if (result.dryRun) {
171+
message = `🔍 DRY RUN - Bulk operation would affect ${result.updated + result.deleted} task(s) (not saved):\n\n`;
172+
if (result.updated > 0) message += `Would update: ${result.updated} task(s)\n`;
173+
if (result.deleted > 0) message += `Would delete: ${result.deleted} task(s)\n`;
174+
message += `\nTo apply changes, remove dryRun or set it to false.`;
175+
} else {
176+
message = `✅ Bulk operation completed!\n\n`;
177+
if (result.updated > 0) message += `Updated: ${result.updated} task(s)\n`;
178+
if (result.deleted > 0) message += `Deleted: ${result.deleted} task(s)\n`;
179+
}
165180

166-
let message = `✅ Bulk operation completed!\n\n`;
167-
message += `Updated: ${result.updated} task(s)\n`;
168181
if (result.errors.length > 0) {
169182
message += `\nErrors (${result.errors.length}):\n`;
170183
result.errors.forEach((err, idx) => {

0 commit comments

Comments
 (0)