mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-30 22:13:28 +08:00
fix: keep loop status scans fail-soft
This commit is contained in:
committed by
Affaan Mustafa
parent
fb6cc8548b
commit
9aace2e6fe
@@ -135,21 +135,32 @@ function getNow(options = {}) {
|
|||||||
return now;
|
return now;
|
||||||
}
|
}
|
||||||
|
|
||||||
function walkJsonlFiles(dir, files = []) {
|
function walkJsonlFiles(dir, result = { errors: [], files: [] }) {
|
||||||
if (!fs.existsSync(dir)) {
|
if (!fs.existsSync(dir)) {
|
||||||
return files;
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
let entries;
|
||||||
|
try {
|
||||||
|
entries = fs.readdirSync(dir, { withFileTypes: true });
|
||||||
|
} catch (error) {
|
||||||
|
result.errors.push({
|
||||||
|
code: error.code || null,
|
||||||
|
message: error.message,
|
||||||
|
transcriptPath: dir,
|
||||||
|
});
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
const fullPath = path.join(dir, entry.name);
|
const fullPath = path.join(dir, entry.name);
|
||||||
if (entry.isDirectory()) {
|
if (entry.isDirectory()) {
|
||||||
walkJsonlFiles(fullPath, files);
|
walkJsonlFiles(fullPath, result);
|
||||||
} else if (entry.isFile() && entry.name.endsWith('.jsonl')) {
|
} else if (entry.isFile() && entry.name.endsWith('.jsonl')) {
|
||||||
files.push(fullPath);
|
result.files.push(fullPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return files;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function findTranscriptPaths(options = {}) {
|
function findTranscriptPaths(options = {}) {
|
||||||
@@ -164,10 +175,11 @@ function findTranscriptPaths(options = {}) {
|
|||||||
|
|
||||||
const homeDir = getHomeDir(normalizedOptions);
|
const homeDir = getHomeDir(normalizedOptions);
|
||||||
const transcriptRoot = path.join(homeDir, '.claude', 'projects');
|
const transcriptRoot = path.join(homeDir, '.claude', 'projects');
|
||||||
const errors = [];
|
const walkResult = walkJsonlFiles(transcriptRoot);
|
||||||
|
const errors = [...walkResult.errors];
|
||||||
const transcriptEntries = [];
|
const transcriptEntries = [];
|
||||||
|
|
||||||
for (const transcriptPath of walkJsonlFiles(transcriptRoot)) {
|
for (const transcriptPath of walkResult.files) {
|
||||||
try {
|
try {
|
||||||
transcriptEntries.push({
|
transcriptEntries.push({
|
||||||
transcriptPath,
|
transcriptPath,
|
||||||
@@ -345,6 +357,10 @@ function buildRecommendation(signals) {
|
|||||||
return 'Open the transcript or interrupt the parked session; the scheduled wake is overdue.';
|
return 'Open the transcript or interrupt the parked session; the scheduled wake is overdue.';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (signals.some(signal => signal.type === 'transcript_parse_errors')) {
|
||||||
|
return 'Inspect the transcript; some JSONL lines could not be parsed.';
|
||||||
|
}
|
||||||
|
|
||||||
return 'No stale ScheduleWakeup or Bash waits detected.';
|
return 'No stale ScheduleWakeup or Bash waits detected.';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -454,6 +470,13 @@ function analyzeTranscript(transcriptPath, options = {}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (parseErrors > 0) {
|
||||||
|
signals.push({
|
||||||
|
count: parseErrors,
|
||||||
|
type: 'transcript_parse_errors',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
eventCount: entries.length,
|
eventCount: entries.length,
|
||||||
lastEventAt: toIso(lastEventAt),
|
lastEventAt: toIso(lastEventAt),
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const path = require('path');
|
|||||||
const { execFileSync } = require('child_process');
|
const { execFileSync } = require('child_process');
|
||||||
|
|
||||||
const SCRIPT = path.join(__dirname, '..', '..', 'scripts', 'loop-status.js');
|
const SCRIPT = path.join(__dirname, '..', '..', 'scripts', 'loop-status.js');
|
||||||
const { analyzeTranscript } = require('../../scripts/loop-status');
|
const { analyzeTranscript, buildStatus } = require('../../scripts/loop-status');
|
||||||
const NOW = '2026-04-30T10:00:00.000Z';
|
const NOW = '2026-04-30T10:00:00.000Z';
|
||||||
|
|
||||||
function run(args = [], options = {}) {
|
function run(args = [], options = {}) {
|
||||||
@@ -313,6 +313,71 @@ function runTests() {
|
|||||||
assert.strictEqual(payload.errors[0].transcriptPath, missingTranscript);
|
assert.strictEqual(payload.errors[0].transcriptPath, missingTranscript);
|
||||||
})) passed++; else failed++;
|
})) passed++; else failed++;
|
||||||
|
|
||||||
|
if (test('continues when one transcript directory cannot be read', () => {
|
||||||
|
const homeDir = createTempHome();
|
||||||
|
const blockedDir = path.join(homeDir, '.claude', 'projects', '-blocked-project');
|
||||||
|
const originalReaddirSync = fs.readdirSync;
|
||||||
|
|
||||||
|
try {
|
||||||
|
writeTranscript(homeDir, '-Users-affoon-project-readable', 'session-readable.jsonl', [
|
||||||
|
toolResult('2026-04-30T09:41:00.000Z', 'session-readable', 'toolu_done', 'done'),
|
||||||
|
]);
|
||||||
|
fs.mkdirSync(blockedDir, { recursive: true });
|
||||||
|
fs.readdirSync = (dir, options) => {
|
||||||
|
if (path.resolve(dir) === path.resolve(blockedDir)) {
|
||||||
|
const error = new Error('permission denied');
|
||||||
|
error.code = 'EACCES';
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
return originalReaddirSync(dir, options);
|
||||||
|
};
|
||||||
|
|
||||||
|
const payload = buildStatus({ home: homeDir, now: NOW });
|
||||||
|
|
||||||
|
assert.strictEqual(payload.sessions.length, 1);
|
||||||
|
assert.strictEqual(payload.sessions[0].sessionId, 'session-readable');
|
||||||
|
assert.strictEqual(payload.errors.length, 1);
|
||||||
|
assert.strictEqual(payload.errors[0].code, 'EACCES');
|
||||||
|
assert.strictEqual(payload.errors[0].transcriptPath, blockedDir);
|
||||||
|
} finally {
|
||||||
|
fs.readdirSync = originalReaddirSync;
|
||||||
|
fs.rmSync(homeDir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
})) passed++; else failed++;
|
||||||
|
|
||||||
|
if (test('reports malformed JSONL lines as an attention signal', () => {
|
||||||
|
const homeDir = createTempHome();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const transcriptDir = path.join(homeDir, '.claude', 'projects', '-Users-affoon-project-malformed');
|
||||||
|
fs.mkdirSync(transcriptDir, { recursive: true });
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(transcriptDir, 'session-malformed.jsonl'),
|
||||||
|
[
|
||||||
|
JSON.stringify({
|
||||||
|
timestamp: '2026-04-30T09:55:00.000Z',
|
||||||
|
sessionId: 'session-malformed',
|
||||||
|
message: { role: 'assistant', content: [{ type: 'text', text: 'partial log' }] },
|
||||||
|
}),
|
||||||
|
'{"timestamp":',
|
||||||
|
].join('\n') + '\n',
|
||||||
|
'utf8'
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = run(['--home', homeDir, '--now', NOW, '--json']);
|
||||||
|
|
||||||
|
assert.strictEqual(result.code, 0, result.stderr);
|
||||||
|
const payload = parsePayload(result.stdout);
|
||||||
|
assert.strictEqual(payload.sessions[0].state, 'attention');
|
||||||
|
assert.ok(payload.sessions[0].signals.some(signal => (
|
||||||
|
signal.type === 'transcript_parse_errors'
|
||||||
|
&& signal.count === 1
|
||||||
|
)));
|
||||||
|
} finally {
|
||||||
|
fs.rmSync(homeDir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
})) passed++; else failed++;
|
||||||
|
|
||||||
if (test('rejects non-integer limit values', () => {
|
if (test('rejects non-integer limit values', () => {
|
||||||
const result = run(['--limit', '1.5']);
|
const result = run(['--limit', '1.5']);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user