fix(extension): workbench.html 0-byte 파괴 방지 — pre-read/pre-write 안전 가드 추가
This commit is contained in:
@@ -411,4 +411,9 @@
|
|||||||
- **해결**: (1) `bridge.py` — 지수 백오프 추가 (1s→2s→4s…60s) + `Retry-After` 헤더 지원 + `is_rate_limited` 프로퍼티, (2) `gateway.py` — rate limit 10→30으로 완화 + `Retry-After` 헤더 응답, (3) `collector.py` — 모든 루프에서 `is_rate_limited` 체크 + response 폴링에 0.2초 인터-리퀘스트 딜레이
|
- **해결**: (1) `bridge.py` — 지수 백오프 추가 (1s→2s→4s…60s) + `Retry-After` 헤더 지원 + `is_rate_limited` 프로퍼티, (2) `gateway.py` — rate limit 10→30으로 완화 + `Retry-After` 헤더 응답, (3) `collector.py` — 모든 루프에서 `is_rate_limited` 체크 + response 폴링에 0.2초 인터-리퀘스트 딜레이
|
||||||
- **주의**: AG 먹통은 봇 자체가 유발한 문제. Extension이나 AG 내부를 건드린 것이 아님. 봇을 끄고 AG를 재시작하면 정상 복구 가능
|
- **주의**: AG 먹통은 봇 자체가 유발한 문제. Extension이나 AG 내부를 건드린 것이 아님. 봇을 끄고 AG를 재시작하면 정상 복구 가능
|
||||||
|
|
||||||
|
### [2026-03-12] workbench.html 0-byte 파괴 — AG 새 창 먹통
|
||||||
|
- **증상**: AG 새 창 열면 화면 먹통 (빈 화면). 봇을 꺼도 복구 안 됨
|
||||||
|
- **원인**: 멀티 윈도우 환경(gravity_control + variet_agent + edf)에서 3개 Extension 인스턴스가 동시에 `workbench.html`을 읽고/패치/쓰기. 한 인스턴스가 `writeFileSync` 중에 다른 인스턴스가 `readFileSync` → 빈 문자열 반환 → 파이프라인 처리 후 0 bytes로 덮어쓰기
|
||||||
|
- **해결**: (1) `workbench-jetski-agent.html`에서 복원 (`jetskiAgent.js` → `workbench.js` 치환), (2) `product.json` 체크섬 갱신, (3) `CachedData` 삭제, (4) Extension에 pre-read/pre-write 안전 가드 추가 (500 bytes 미만 또는 `<!DOCTYPE html>` 없으면 패치 스킵)
|
||||||
|
- **주의**: **AG 풀 재시작 필수**. 멀티 윈도우 환경에서 HTML 패치 race condition은 근본적으로 파일 잠금 없이는 완전 해결 불가 — 안전 가드로 피해 최소화
|
||||||
|
|
||||||
|
|||||||
@@ -3,3 +3,4 @@
|
|||||||
| # | 시간 | 작업 설명 | 커밋 | 상태 |
|
| # | 시간 | 작업 설명 | 커밋 | 상태 |
|
||||||
|---|------|----------|------|------|
|
|---|------|----------|------|------|
|
||||||
| 001 | 00:34~00:47 | 429 Rate Limit 무한 루프 디버깅 — 지수 백오프 + rate limit 완화 + Collector 폴링 보호 | `d9b36cf` | ✅ |
|
| 001 | 00:34~00:47 | 429 Rate Limit 무한 루프 디버깅 — 지수 백오프 + rate limit 완화 + Collector 폴링 보호 | `d9b36cf` | ✅ |
|
||||||
|
| 002 | 16:45~17:04 | workbench.html 0-byte 파괴 복구 — 멀티 인스턴스 race condition 방지 안전 가드 추가 | `(pending)` | ✅ |
|
||||||
|
|||||||
@@ -459,6 +459,12 @@ async function setupApprovalObserver() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let html = fs.readFileSync(htmlPath, 'utf8');
|
let html = fs.readFileSync(htmlPath, 'utf8');
|
||||||
|
// SAFETY: Refuse to patch if file is empty or suspiciously small
|
||||||
|
// (race condition: another extension instance may be mid-write)
|
||||||
|
if (html.length < 500 || !html.includes('<!DOCTYPE html>')) {
|
||||||
|
logToFile(`[OBSERVER] ${htmlFileName} appears corrupt or empty (${html.length} bytes) — SKIPPING to prevent further damage`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
// CRITICAL: Patch CSP to allow inline scripts.
|
// CRITICAL: Patch CSP to allow inline scripts.
|
||||||
// Default CSP has script-src 'self' 'unsafe-eval' blob: — NO 'unsafe-inline'.
|
// Default CSP has script-src 'self' 'unsafe-eval' blob: — NO 'unsafe-inline'.
|
||||||
// Without 'unsafe-inline', all inline <script> tags are silently blocked.
|
// Without 'unsafe-inline', all inline <script> tags are silently blocked.
|
||||||
@@ -490,6 +496,11 @@ async function setupApprovalObserver() {
|
|||||||
html = html.replace('</html>', `\n${inlineMarkerStart}\n<script>\n${combinedScript}\n</script>\n${inlineMarkerEnd}\n</html>`);
|
html = html.replace('</html>', `\n${inlineMarkerStart}\n<script>\n${combinedScript}\n</script>\n${inlineMarkerEnd}\n</html>`);
|
||||||
logToFile(`[OBSERVER] ${htmlFileName} inline script INSERTED`);
|
logToFile(`[OBSERVER] ${htmlFileName} inline script INSERTED`);
|
||||||
}
|
}
|
||||||
|
// SAFETY: Final validation before write — never write empty or invalid HTML
|
||||||
|
if (html.length < 500 || !html.includes('<!DOCTYPE html>')) {
|
||||||
|
logToFile(`[OBSERVER] ${htmlFileName} WOULD BE CORRUPT after patching (${html.length} bytes) — ABORTING write`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
fs.writeFileSync(htmlPath, html, 'utf8');
|
fs.writeFileSync(htmlPath, html, 'utf8');
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -441,6 +441,13 @@ async function setupApprovalObserver() {
|
|||||||
}
|
}
|
||||||
let html = fs.readFileSync(htmlPath, 'utf8');
|
let html = fs.readFileSync(htmlPath, 'utf8');
|
||||||
|
|
||||||
|
// SAFETY: Refuse to patch if file is empty or suspiciously small
|
||||||
|
// (race condition: another extension instance may be mid-write)
|
||||||
|
if (html.length < 500 || !html.includes('<!DOCTYPE html>')) {
|
||||||
|
logToFile(`[OBSERVER] ${htmlFileName} appears corrupt or empty (${html.length} bytes) — SKIPPING to prevent further damage`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// CRITICAL: Patch CSP to allow inline scripts.
|
// CRITICAL: Patch CSP to allow inline scripts.
|
||||||
// Default CSP has script-src 'self' 'unsafe-eval' blob: — NO 'unsafe-inline'.
|
// Default CSP has script-src 'self' 'unsafe-eval' blob: — NO 'unsafe-inline'.
|
||||||
// Without 'unsafe-inline', all inline <script> tags are silently blocked.
|
// Without 'unsafe-inline', all inline <script> tags are silently blocked.
|
||||||
@@ -483,6 +490,11 @@ async function setupApprovalObserver() {
|
|||||||
`\n${inlineMarkerStart}\n<script>\n${combinedScript}\n</script>\n${inlineMarkerEnd}\n</html>`);
|
`\n${inlineMarkerStart}\n<script>\n${combinedScript}\n</script>\n${inlineMarkerEnd}\n</html>`);
|
||||||
logToFile(`[OBSERVER] ${htmlFileName} inline script INSERTED`);
|
logToFile(`[OBSERVER] ${htmlFileName} inline script INSERTED`);
|
||||||
}
|
}
|
||||||
|
// SAFETY: Final validation before write — never write empty or invalid HTML
|
||||||
|
if (html.length < 500 || !html.includes('<!DOCTYPE html>')) {
|
||||||
|
logToFile(`[OBSERVER] ${htmlFileName} WOULD BE CORRUPT after patching (${html.length} bytes) — ABORTING write`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
fs.writeFileSync(htmlPath, html, 'utf8');
|
fs.writeFileSync(htmlPath, html, 'utf8');
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
logToFile(`[OBSERVER] ${htmlFileName} patch error: ${e.message}`);
|
logToFile(`[OBSERVER] ${htmlFileName} patch error: ${e.message}`);
|
||||||
|
|||||||
Reference in New Issue
Block a user