0cd853e330
Under S3 the chat of a member's session lagged a whole turn behind the terminal: claude-home.js located transcripts with fs.existsSync, which is access(2) — run by the kernel with capabilities cleared — so the unit's CAP_DAC_READ_SEARCH could not see past the member's 0700 ~/.claude/projects. Every lookup fell back to the import-time snapshot, which is only refreshed now and then. Transcript finders and the /transcripts route now use stat.
67 lines
2.7 KiB
JavaScript
67 lines
2.7 KiB
JavaScript
/**
|
|
* @file Regression: transcript lookups in claude-home.js find a file by
|
|
* `stat`, never `fs.existsSync`. existsSync is access(2), which the kernel runs
|
|
* with the capabilities cleared, so under S3 it reported a member's live
|
|
* transcript (behind their 0700 `~/.claude/projects`) as missing and the chat
|
|
* served the stale import-time snapshot — a whole turn shown in the terminal
|
|
* never reached the chat. Here `fs.existsSync` is stubbed to answer "no" for
|
|
* everything under the temp Claude home, as access(2) does for the unit.
|
|
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
|
*/
|
|
|
|
const os = require("node:os");
|
|
const fs = require("node:fs");
|
|
const path = require("node:path");
|
|
const { describe, it, before, after } = require("node:test");
|
|
const assert = require("node:assert/strict");
|
|
|
|
const ROOT = fs.mkdtempSync(path.join(os.tmpdir(), "agenthub-access-blind-"));
|
|
const SAVED_HOME = process.env.CLAUDE_HOME;
|
|
process.env.CLAUDE_HOME = ROOT;
|
|
|
|
const ch = require("../lib/claude-home");
|
|
|
|
const SID = "11111111-2222-3333-4444-555555555555";
|
|
const CWD = "/work/demo-repo";
|
|
const PROJECT = path.join(ROOT, "projects", ch.encodeCwd(CWD));
|
|
const MAIN = path.join(PROJECT, `${SID}.jsonl`);
|
|
const SUB = path.join(PROJECT, SID, "subagents", "agent-abc.jsonl");
|
|
const NESTED = path.join(PROJECT, SID, "subagents", "workflows", "wf_1", "agent-xyz.jsonl");
|
|
|
|
const realExists = fs.existsSync;
|
|
|
|
describe("claude-home transcript lookups when access(2) is blind", () => {
|
|
before(() => {
|
|
for (const f of [MAIN, SUB, NESTED]) {
|
|
fs.mkdirSync(path.dirname(f), { recursive: true });
|
|
fs.writeFileSync(f, "{}\n");
|
|
}
|
|
fs.existsSync = (p) => (String(p).startsWith(ROOT) ? false : realExists(p));
|
|
});
|
|
after(() => {
|
|
fs.existsSync = realExists;
|
|
if (SAVED_HOME === undefined) delete process.env.CLAUDE_HOME;
|
|
else process.env.CLAUDE_HOME = SAVED_HOME;
|
|
fs.rmSync(ROOT, { recursive: true, force: true });
|
|
});
|
|
|
|
it("getTranscriptPath finds the live main transcript", () => {
|
|
assert.equal(ch.getTranscriptPath(SID, CWD), MAIN);
|
|
});
|
|
|
|
it("findTranscriptPath finds it by scanning", () => {
|
|
assert.equal(ch.findTranscriptPath(SID, CWD), MAIN);
|
|
});
|
|
|
|
it("subagent transcripts (flat and nested Workflow) are found", () => {
|
|
assert.equal(ch.findSubagentTranscriptPath(SID, "abc", null, CWD), SUB);
|
|
assert.equal(ch.findSubagentTranscriptPath(SID, "xyz", "wf_1", CWD), NESTED);
|
|
assert.equal(ch.findSubagentTranscriptPath(SID, "xyz", null, CWD), NESTED);
|
|
});
|
|
|
|
it("a missing transcript is still reported missing", () => {
|
|
assert.equal(ch.getTranscriptPath("no-such-session", CWD), null);
|
|
assert.equal(ch.exists(path.join(ROOT, "nope")), false);
|
|
});
|
|
});
|