fix: address PR #1641 review comments (round 3)
- Fix migration version conflict: addSessionPlatformSourceColumn now uses v25 - Sanitize observation titles in file-context deny reason (strip newlines, limit length) - Guard json_each() with LIKE '[%' check for legacy bare-path rows - Guard /stream SSE endpoint with 503 before DB initialization - Scope bun-runner signal exit handling to start subcommand only - Normalize platformSource at route boundary in DataRoutes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -196,7 +196,7 @@ child.on('close', (code, signal) => {
|
|||||||
// Fix #1505: When the "start" subcommand forks a daemon, the parent bun
|
// Fix #1505: When the "start" subcommand forks a daemon, the parent bun
|
||||||
// process may be killed by signal (e.g. SIGKILL, exit code 137). The daemon
|
// process may be killed by signal (e.g. SIGKILL, exit code 137). The daemon
|
||||||
// is running fine — treat signal-based exits for "start" as success.
|
// is running fine — treat signal-based exits for "start" as success.
|
||||||
if (signal || (code > 128 && args.includes('start'))) {
|
if ((signal || code > 128) && args.includes('start')) {
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
process.exit(code || 0);
|
process.exit(code || 0);
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -150,7 +150,7 @@ function formatFileTimeline(observations: ObservationRow[], filePath: string): s
|
|||||||
const chronological = [...dayObservations].sort((a, b) => a.created_at_epoch - b.created_at_epoch);
|
const chronological = [...dayObservations].sort((a, b) => a.created_at_epoch - b.created_at_epoch);
|
||||||
lines.push(`### ${day}`);
|
lines.push(`### ${day}`);
|
||||||
for (const obs of chronological) {
|
for (const obs of chronological) {
|
||||||
const title = obs.title || 'Untitled';
|
const title = (obs.title || 'Untitled').replace(/[\r\n\t]/g, ' ').slice(0, 120);
|
||||||
const icon = TYPE_ICONS[obs.type] || '\u2753';
|
const icon = TYPE_ICONS[obs.type] || '\u2753';
|
||||||
const time = compactTime(formatTime(obs.created_at_epoch));
|
const time = compactTime(formatTime(obs.created_at_epoch));
|
||||||
lines.push(`${obs.id} ${time} ${icon} ${title}`);
|
lines.push(`${obs.id} ${time} ${icon} ${title}`);
|
||||||
|
|||||||
@@ -894,14 +894,14 @@ export class MigrationRunner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add platform_source column to sdk_sessions for Claude/Codex isolation (migration 24)
|
* Add platform_source column to sdk_sessions for Claude/Codex isolation (migration 25)
|
||||||
*/
|
*/
|
||||||
private addSessionPlatformSourceColumn(): void {
|
private addSessionPlatformSourceColumn(): void {
|
||||||
const tableInfo = this.db.query('PRAGMA table_info(sdk_sessions)').all() as TableColumnInfo[];
|
const tableInfo = this.db.query('PRAGMA table_info(sdk_sessions)').all() as TableColumnInfo[];
|
||||||
const hasColumn = tableInfo.some(col => col.name === 'platform_source');
|
const hasColumn = tableInfo.some(col => col.name === 'platform_source');
|
||||||
const indexInfo = this.db.query('PRAGMA index_list(sdk_sessions)').all() as IndexInfo[];
|
const indexInfo = this.db.query('PRAGMA index_list(sdk_sessions)').all() as IndexInfo[];
|
||||||
const hasIndex = indexInfo.some(index => index.name === 'idx_sdk_sessions_platform_source');
|
const hasIndex = indexInfo.some(index => index.name === 'idx_sdk_sessions_platform_source');
|
||||||
const applied = this.db.prepare('SELECT version FROM schema_versions WHERE version = ?').get(24) as SchemaVersion | undefined;
|
const applied = this.db.prepare('SELECT version FROM schema_versions WHERE version = ?').get(25) as SchemaVersion | undefined;
|
||||||
|
|
||||||
if (applied && hasColumn && hasIndex) return;
|
if (applied && hasColumn && hasIndex) return;
|
||||||
|
|
||||||
@@ -920,6 +920,6 @@ export class MigrationRunner {
|
|||||||
this.db.run('CREATE INDEX IF NOT EXISTS idx_sdk_sessions_platform_source ON sdk_sessions(platform_source)');
|
this.db.run('CREATE INDEX IF NOT EXISTS idx_sdk_sessions_platform_source ON sdk_sessions(platform_source)');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.db.prepare('INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)').run(24, new Date().toISOString());
|
this.db.prepare('INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)').run(25, new Date().toISOString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -140,8 +140,8 @@ export function getObservationsByFilePath(
|
|||||||
SELECT *
|
SELECT *
|
||||||
FROM observations
|
FROM observations
|
||||||
WHERE (
|
WHERE (
|
||||||
EXISTS (SELECT 1 FROM json_each(files_read) WHERE value = ?)
|
(files_read LIKE '[%' AND EXISTS (SELECT 1 FROM json_each(files_read) WHERE value = ?))
|
||||||
OR EXISTS (SELECT 1 FROM json_each(files_modified) WHERE value = ?)
|
OR (files_modified LIKE '[%' AND EXISTS (SELECT 1 FROM json_each(files_modified) WHERE value = ?))
|
||||||
)
|
)
|
||||||
${projectClause}
|
${projectClause}
|
||||||
ORDER BY created_at_epoch DESC
|
ORDER BY created_at_epoch DESC
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { SessionManager } from '../../SessionManager.js';
|
|||||||
import { SSEBroadcaster } from '../../SSEBroadcaster.js';
|
import { SSEBroadcaster } from '../../SSEBroadcaster.js';
|
||||||
import type { WorkerService } from '../../../worker-service.js';
|
import type { WorkerService } from '../../../worker-service.js';
|
||||||
import { BaseRouteHandler } from '../BaseRouteHandler.js';
|
import { BaseRouteHandler } from '../BaseRouteHandler.js';
|
||||||
|
import { normalizePlatformSource } from '../../../../shared/platform-source.js';
|
||||||
import { getObservationsByFilePath } from '../../../sqlite/observations/get.js';
|
import { getObservationsByFilePath } from '../../../sqlite/observations/get.js';
|
||||||
|
|
||||||
export class DataRoutes extends BaseRouteHandler {
|
export class DataRoutes extends BaseRouteHandler {
|
||||||
@@ -281,7 +282,8 @@ export class DataRoutes extends BaseRouteHandler {
|
|||||||
*/
|
*/
|
||||||
private handleGetProjects = this.wrapHandler((req: Request, res: Response): void => {
|
private handleGetProjects = this.wrapHandler((req: Request, res: Response): void => {
|
||||||
const store = this.dbManager.getSessionStore();
|
const store = this.dbManager.getSessionStore();
|
||||||
const platformSource = req.query.platformSource as string | undefined;
|
const rawPlatformSource = req.query.platformSource as string | undefined;
|
||||||
|
const platformSource = rawPlatformSource ? normalizePlatformSource(rawPlatformSource) : undefined;
|
||||||
|
|
||||||
if (platformSource) {
|
if (platformSource) {
|
||||||
const projects = store.getAllProjects(platformSource);
|
const projects = store.getAllProjects(platformSource);
|
||||||
@@ -328,7 +330,8 @@ export class DataRoutes extends BaseRouteHandler {
|
|||||||
const offset = parseInt(req.query.offset as string, 10) || 0;
|
const offset = parseInt(req.query.offset as string, 10) || 0;
|
||||||
const limit = Math.min(parseInt(req.query.limit as string, 10) || 20, 100); // Max 100
|
const limit = Math.min(parseInt(req.query.limit as string, 10) || 20, 100); // Max 100
|
||||||
const project = req.query.project as string | undefined;
|
const project = req.query.project as string | undefined;
|
||||||
const platformSource = req.query.platformSource as string | undefined;
|
const rawPlatformSource = req.query.platformSource as string | undefined;
|
||||||
|
const platformSource = rawPlatformSource ? normalizePlatformSource(rawPlatformSource) : undefined;
|
||||||
|
|
||||||
return { offset, limit, project, platformSource };
|
return { offset, limit, project, platformSource };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,6 +68,14 @@ export class ViewerRoutes extends BaseRouteHandler {
|
|||||||
* SSE stream endpoint
|
* SSE stream endpoint
|
||||||
*/
|
*/
|
||||||
private handleSSEStream = this.wrapHandler((req: Request, res: Response): void => {
|
private handleSSEStream = this.wrapHandler((req: Request, res: Response): void => {
|
||||||
|
// Guard: if DB is not yet initialized, return 503 before registering client
|
||||||
|
try {
|
||||||
|
this.dbManager.getSessionStore();
|
||||||
|
} catch {
|
||||||
|
res.status(503).json({ error: 'Service initializing' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Setup SSE headers
|
// Setup SSE headers
|
||||||
res.setHeader('Content-Type', 'text/event-stream');
|
res.setHeader('Content-Type', 'text/event-stream');
|
||||||
res.setHeader('Cache-Control', 'no-cache');
|
res.setHeader('Cache-Control', 'no-cache');
|
||||||
|
|||||||
Reference in New Issue
Block a user