SPB Git

spb/autoscholar

Public

Policy: desktop-command rule only at command start; workspace reset restores permissions; worker state typing; test report draft

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Simon-Pierre Boucher committed (Oct 30, 2026) parent 0d8280a Browse files

3 changed files +9 −4

modified services/research-runner/src/policy.ts +1 −1
@@ -83,7 +83,7 @@ export const DENIED_COMMAND_PATTERNS: Array<{ re: RegExp; reason: string }> = [
83 { re: /\/var\/autoscholar\/(?!runs\/)/, reason: "worker root outside the run workspace" }, 83 { re: /\/var\/autoscholar\/(?!runs\/)/, reason: "worker root outside the run workspace" },
84 { re: /ANTHROPIC_API_KEY|WORKER_TOKEN|AUTOSCHOLAR_|\.env(\b|$)/, reason: "credential access" }, 84 { re: /ANTHROPIC_API_KEY|WORKER_TOKEN|AUTOSCHOLAR_|\.env(\b|$)/, reason: "credential access" },
85 { 85 {
86− re: /(^|[\s;&|(])(open|say|afplay|screencapture|caffeinate)(\s|$)/, 86+ re: /(^|[;&|]\s*)(open|say|afplay|screencapture|caffeinate)\s+(-|\.|\/|~|https?:)/,
87 reason: "desktop/session interaction", 87 reason: "desktop/session interaction",
88 }, 88 },
89 { 89 {
modified services/research-runner/src/runner.ts +6 −1
@@ -161,7 +161,12 @@ export class ResearchRunner {
161 } 161 }
162 } 162 }
163 if (!resumed) { 163 if (!resumed) {
164− if (existed) await rm(ws, { recursive: true, force: true }); 164+ if (existed) {
165+ await chmod(join(ws, "input"), 0o755).catch(() => {});
166+ for (const f of ["CLAUDE.md", "input/CLAUDE.md", "input/run.json"])
167+ await chmod(join(ws, f), 0o644).catch(() => {});
168+ await rm(ws, { recursive: true, force: true });
169+ }
165 for (const d of [ 170 for (const d of [
166 "input", 171 "input",
167 "plan", 172 "plan",
modified services/worker/src/main.ts +2 −2
@@ -205,10 +205,10 @@ class Worker {
205 } 205 }
206 206
207 private async execute(job: Awaited<ReturnType<WorkerClient["lease"]>>["job"] & object) { 207 private async execute(job: Awaited<ReturnType<WorkerClient["lease"]>>["job"] & object) {
208− const state = { 208+ const state: { fencingToken: number; cancel: boolean; restart?: boolean; events: RunEventInput[] } = {
209 fencingToken: job.fencingToken, 209 fencingToken: job.fencingToken,
210 cancel: job.cancelRequested, 210 cancel: job.cancelRequested,
211− events: [] as RunEventInput[], 211+ events: [],
212 }; 212 };
213 this.running.set(job.runId, state); 213 this.running.set(job.runId, state);
214 const renew = setInterval(async () => { 214 const renew = setInterval(async () => {
215 215
Menu

Theme