Merge pull request #427 from affaan-m/codex/orchestration-harness-skills

fix: harden observe loop prevention
This commit is contained in:
Affaan Mustafa
2026-03-13 02:14:40 -07:00
committed by GitHub
2 changed files with 180 additions and 19 deletions

View File

@@ -82,32 +82,13 @@ if [ -n "$STDIN_CWD" ] && [ -d "$STDIN_CWD" ]; then
export CLAUDE_PROJECT_DIR="$STDIN_CWD"
fi
# ─────────────────────────────────────────────
# Project detection
# ─────────────────────────────────────────────
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SKILL_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
# Source shared project detection helper
# This sets: PROJECT_ID, PROJECT_NAME, PROJECT_ROOT, PROJECT_DIR
source "${SKILL_ROOT}/scripts/detect-project.sh"
PYTHON_CMD="${CLV2_PYTHON_CMD:-$PYTHON_CMD}"
# ─────────────────────────────────────────────
# Configuration
# ─────────────────────────────────────────────
CONFIG_DIR="${HOME}/.claude/homunculus"
OBSERVATIONS_FILE="${PROJECT_DIR}/observations.jsonl"
MAX_FILE_SIZE_MB=10
SENTINEL_FILE="${CLV2_OBSERVER_SENTINEL_FILE:-${PROJECT_ROOT:-$PROJECT_DIR}/.observer.lock}"
write_guard_sentinel() {
printf '%s\n' 'observer paused: confirmation or permission prompt detected; rerun start-observer.sh --reset after reviewing observer.log' > "$SENTINEL_FILE"
}
# Skip if disabled globally
if [ -f "$CONFIG_DIR/disabled" ]; then
exit 0
@@ -119,6 +100,8 @@ fi
# - ECC observing its own Haiku observer sessions (self-loop)
# - ECC observing other tools' automated sessions (e.g. claude-mem)
# - All-night Haiku usage with no human activity
# Run these before project detection so skipped sessions cannot mutate
# project-scoped observer state.
# ─────────────────────────────────────────────
# Env-var checks first (cheapest — no subprocess spawning):
@@ -161,6 +144,26 @@ if [ -n "$STDIN_CWD" ]; then
done
fi
# ─────────────────────────────────────────────
# Project detection
# ─────────────────────────────────────────────
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SKILL_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
# Source shared project detection helper
# This sets: PROJECT_ID, PROJECT_NAME, PROJECT_ROOT, PROJECT_DIR
source "${SKILL_ROOT}/scripts/detect-project.sh"
PYTHON_CMD="${CLV2_PYTHON_CMD:-$PYTHON_CMD}"
OBSERVATIONS_FILE="${PROJECT_DIR}/observations.jsonl"
SENTINEL_FILE="${CLV2_OBSERVER_SENTINEL_FILE:-${PROJECT_ROOT:-$PROJECT_DIR}/.observer.lock}"
write_guard_sentinel() {
printf '%s\n' 'observer paused: confirmation or permission prompt detected; rerun start-observer.sh --reset after reviewing observer.log' > "$SENTINEL_FILE"
}
# Skip if a previous run already aborted due to confirmation/permission prompt.
# This is the circuit-breaker — stops retrying after a non-interactive failure.
if [ -f "$SENTINEL_FILE" ]; then

View File

@@ -136,6 +136,27 @@ function pathsReferToSameLocation(leftPath, rightPath) {
}
}
function createObservePayload(projectDir, overrides = {}) {
return JSON.stringify({
tool_name: 'Bash',
tool_input: { command: 'echo hello' },
tool_response: 'ok',
session_id: 'session-123',
cwd: projectDir,
...overrides
});
}
function listObservationFiles(homeDir) {
const projectsDir = path.join(homeDir, '.claude', 'homunculus', 'projects');
if (!fs.existsSync(projectsDir)) return [];
return fs
.readdirSync(projectsDir)
.map(projectId => path.join(projectsDir, projectId, 'observations.jsonl'))
.filter(observationsPath => fs.existsSync(observationsPath));
}
function createCommandShim(binDir, baseName, logFile) {
fs.mkdirSync(binDir, { recursive: true });
@@ -2414,6 +2435,143 @@ async function runTests() {
}
})) passed++; else failed++;
if (await asyncTest('observe.sh skips non-cli entrypoints without writing observations', async () => {
const homeDir = createTestDir();
const projectDir = createTestDir();
const observePath = path.join(__dirname, '..', '..', 'skills', 'continuous-learning-v2', 'hooks', 'observe.sh');
try {
const result = await runShellScript(observePath, ['post'], createObservePayload(projectDir, { session_id: 'session-non-cli' }), {
HOME: homeDir,
CLAUDE_PROJECT_DIR: projectDir,
CLAUDE_CODE_ENTRYPOINT: 'sdk'
}, projectDir);
assert.strictEqual(result.code, 0, `observe.sh should exit successfully for non-cli entrypoints, stderr: ${result.stderr}`);
assert.ok(!fs.existsSync(path.join(homeDir, '.claude', 'homunculus', 'projects')), 'non-cli entrypoints should exit before project detection runs');
assert.deepStrictEqual(listObservationFiles(homeDir), [], 'non-cli entrypoints should not write observations');
} finally {
cleanupTestDir(homeDir);
cleanupTestDir(projectDir);
}
})) passed++; else failed++;
if (await asyncTest('observe.sh skips ECC_SKIP_OBSERVE sessions without writing observations', async () => {
const homeDir = createTestDir();
const projectDir = createTestDir();
const observePath = path.join(__dirname, '..', '..', 'skills', 'continuous-learning-v2', 'hooks', 'observe.sh');
try {
const result = await runShellScript(observePath, ['post'], createObservePayload(projectDir, { session_id: 'session-skip-env' }), {
HOME: homeDir,
CLAUDE_PROJECT_DIR: projectDir,
ECC_SKIP_OBSERVE: '1'
}, projectDir);
assert.strictEqual(result.code, 0, `observe.sh should exit successfully when ECC_SKIP_OBSERVE=1, stderr: ${result.stderr}`);
assert.ok(!fs.existsSync(path.join(homeDir, '.claude', 'homunculus', 'projects')), 'ECC_SKIP_OBSERVE should exit before project detection runs');
assert.deepStrictEqual(listObservationFiles(homeDir), [], 'ECC_SKIP_OBSERVE should suppress observation writes');
} finally {
cleanupTestDir(homeDir);
cleanupTestDir(projectDir);
}
})) passed++; else failed++;
if (await asyncTest('observe.sh skips subagent payloads with agent_id without writing observations', async () => {
const homeDir = createTestDir();
const projectDir = createTestDir();
const observePath = path.join(__dirname, '..', '..', 'skills', 'continuous-learning-v2', 'hooks', 'observe.sh');
try {
const result = await runShellScript(
observePath,
['post'],
createObservePayload(projectDir, { session_id: 'session-agent', agent_id: 'agent-123' }),
{
HOME: homeDir,
CLAUDE_PROJECT_DIR: projectDir
},
projectDir
);
assert.strictEqual(result.code, 0, `observe.sh should exit successfully for subagent sessions, stderr: ${result.stderr}`);
assert.ok(!fs.existsSync(path.join(homeDir, '.claude', 'homunculus', 'projects')), 'subagent sessions should exit before project detection runs');
assert.deepStrictEqual(listObservationFiles(homeDir), [], 'subagent sessions should not write observations');
} finally {
cleanupTestDir(homeDir);
cleanupTestDir(projectDir);
}
})) passed++; else failed++;
if (await asyncTest('observe.sh skips default observer-session paths without writing observations', async () => {
const homeDir = createTestDir();
const projectRoot = createTestDir();
const projectDir = path.join(projectRoot, 'observer-sessions-worker');
const observePath = path.join(__dirname, '..', '..', 'skills', 'continuous-learning-v2', 'hooks', 'observe.sh');
fs.mkdirSync(projectDir, { recursive: true });
try {
const result = await runShellScript(observePath, ['post'], createObservePayload(projectDir, { session_id: 'session-default-skip-path' }), {
HOME: homeDir,
CLAUDE_PROJECT_DIR: projectDir
}, projectDir);
assert.strictEqual(result.code, 0, `observe.sh should exit successfully for default skip paths, stderr: ${result.stderr}`);
assert.ok(!fs.existsSync(path.join(homeDir, '.claude', 'homunculus', 'projects')), 'default skip paths should exit before project detection runs');
assert.deepStrictEqual(listObservationFiles(homeDir), [], 'default skip paths should suppress observation writes');
} finally {
cleanupTestDir(homeDir);
cleanupTestDir(projectRoot);
}
})) passed++; else failed++;
if (await asyncTest('observe.sh trims custom skip-path patterns before matching', async () => {
const homeDir = createTestDir();
const projectRoot = createTestDir();
const projectDir = path.join(projectRoot, 'custom-observer-session');
const observePath = path.join(__dirname, '..', '..', 'skills', 'continuous-learning-v2', 'hooks', 'observe.sh');
fs.mkdirSync(projectDir, { recursive: true });
try {
const result = await runShellScript(observePath, ['post'], createObservePayload(projectDir, { session_id: 'session-custom-skip-path' }), {
HOME: homeDir,
CLAUDE_PROJECT_DIR: projectDir,
ECC_OBSERVE_SKIP_PATHS: ' custom-observer-session , , '
}, projectDir);
assert.strictEqual(result.code, 0, `observe.sh should exit successfully for custom skip paths, stderr: ${result.stderr}`);
assert.ok(!fs.existsSync(path.join(homeDir, '.claude', 'homunculus', 'projects')), 'custom skip paths should exit before project detection runs');
assert.deepStrictEqual(listObservationFiles(homeDir), [], 'trimmed custom skip paths should suppress observation writes');
} finally {
cleanupTestDir(homeDir);
cleanupTestDir(projectRoot);
}
})) passed++; else failed++;
if (await asyncTest('observe.sh ignores empty skip-path entries so normal paths still record observations', async () => {
const homeDir = createTestDir();
const projectDir = createTestDir();
const observePath = path.join(__dirname, '..', '..', 'skills', 'continuous-learning-v2', 'hooks', 'observe.sh');
try {
const result = await runShellScript(observePath, ['post'], createObservePayload(projectDir, { session_id: 'session-empty-skip-paths' }), {
HOME: homeDir,
CLAUDE_PROJECT_DIR: projectDir,
ECC_OBSERVE_SKIP_PATHS: ' , , '
}, projectDir);
assert.strictEqual(result.code, 0, `observe.sh should exit successfully when skip-path entries are empty, stderr: ${result.stderr}`);
const observationFiles = listObservationFiles(homeDir);
assert.strictEqual(observationFiles.length, 1, 'empty skip-path entries should not suppress normal observations');
const observations = fs.readFileSync(observationFiles[0], 'utf8').trim().split('\n').filter(Boolean);
assert.ok(observations.length > 0, 'normal sessions should still append observations when skip-path entries are empty');
} finally {
cleanupTestDir(homeDir);
cleanupTestDir(projectDir);
}
})) passed++; else failed++;
if (await asyncTest('matches .tsx extension for type checking', async () => {
const testDir = createTestDir();
const testFile = path.join(testDir, 'component.tsx');