Claude/cpu guard script dn d8u - #133
Conversation
…eout Telegraf's long-polling uses getUpdates with timeout=50 (Telegram holds the connection for up to 50 seconds waiting for new updates). The callApi patch was wrapping ALL methods — including getUpdates — with globalThrottle, which has a hard 15-second race. This fired on every poll cycle, throwing 'rateLimiter: API call timed out after 15000ms', causing bot.launch() to reject and systemd to enter an infinite restart loop. Fix: add getUpdates (and Telegraf startup/lifecycle calls: getMe, deleteWebhook, setWebhook, getWebhookInfo, close, logOut) to the _MANAGED bypass set so they call the original callApi directly without the timeout wrapper. All 60 unit tests pass. Bot loads cleanly in CI mode. https://claude.ai/code/session_01WpEqAiDbpqnBywMjYXJHf4
…hod sets Two fixes: 1. index.js — ensureQaArtifacts(), persistQaProviderState(), ensureQaDirs(): All QA file writes are now wrapped in try/catch and treated as non-fatal. The bot service runs as the 'runewager' system user but qa/ is owned by root, so writeFileSync was throwing EACCES on every startup, which was caught by startBot()'s outer try/catch and called process.exit(1). 2. telegramSafe.js — callApi bypass list refactored (code review feedback): The inline _MANAGED set is replaced with three named, exported constants: - LONG_POLL_METHODS : getUpdates (must bypass 15 s timeout) - LIFECYCLE_METHODS : getMe, deleteWebhook, setWebhook, etc. - INDIVIDUALLY_PATCHED_METHODS : sendMessage, sendPhoto, etc. Combined into CALLAPI_BYPASS_METHODS used by the callApi patch. All sets are exported so test suites can assert bypass membership without requiring a live Telegraf instance. All 60 unit tests pass. Module loads cleanly in CI mode. https://claude.ai/code/session_01WpEqAiDbpqnBywMjYXJHf4
…e conflict Addresses code review feedback: index.js: - Added QA_FS_SUPPRESS constant (EACCES, EPERM, EROFS, EEXIST) — the set of expected error codes when qa/ is owned by root but the service runs as the runewager user. Defined once, shared by all three QA helpers. - ensureQaDirs: replace silent catch-all with per-directory loop; unexpected error codes (e.g. ENOENT, ENOMEM) are logged as warnings so they remain visible; permission codes are silently skipped. - persistQaProviderState: catch checks err.code — returns silently for permission errors, logs a warning for any other code (e.g. JSON failure). - ensureQaArtifacts: same pattern — unexpected errors are logged at warn level; permission errors (EACCES/EPERM/EROFS) are intentionally silent. telegramSafe.js: - Resolved merge conflict: kept the CALLAPI_BYPASS_METHODS module-level constants introduced in the previous commit; dropped the inline _MANAGED block that arrived from main during the merge. All 60 unit tests pass. https://claude.ai/code/session_01WpEqAiDbpqnBywMjYXJHf4
External code or tests could accidentally mutate the exported Sets (LONG_POLL_METHODS, LIFECYCLE_METHODS, etc.) which would silently alter runtime bypass behavior in callApi. Export frozen array snapshots instead — callers can still iterate and use .includes() for membership checks; the internal module-level Sets remain mutable for runtime use by the callApi patch. All 60 unit tests pass. https://claude.ai/code/session_01WpEqAiDbpqnBywMjYXJHf4
…=false, UMask=0022
ProtectSystem=strict makes the ENTIRE filesystem read-only (including /var),
requiring every directory Node.js touches to be explicitly listed in
ReadWritePaths. This caused silent crashes under systemd while manual
`node index.js` worked fine (no sandbox).
Changes:
- ProtectSystem=strict → full
/usr, /boot, /etc stay read-only; /var (where the app lives) is fully
writable. No more brittle per-directory ReadWritePaths maintenance.
- ProtectHome=true → false
Service runs as root; /root is root's home. Node/npm use /root for
internal caches and temp files — blocking it caused subtle startup
failures that only appeared under systemd sandboxing.
- UMask=0077 → 0022
0077 (owner-only) made log files mode 600, unreadable by log-tailing
utilities running as non-root. 0022 gives the standard 644/755.
- ReadWritePaths kept as belt-and-suspenders documentation; functionally
redundant under ProtectSystem=full but makes intent explicit and guards
against future tightening.
- Expanded inline comments explain each directive's rationale so the next
operator understands the trade-off before changing settings.
All 60 unit tests pass.
https://claude.ai/code/session_01WpEqAiDbpqnBywMjYXJHf4
…s catch blocks
EEXIST fix:
fs.mkdirSync with { recursive: true } never throws EEXIST for an existing
directory — it silently succeeds. The only way EEXIST reaches these catch
blocks is if a *file* exists at the expected directory path, which is a real
misconfiguration that will silently swallow all QA artifact writes. Removing
EEXIST from QA_FS_SUPPRESS lets that case surface as a logged warning.
Comment updated to explain the exclusion.
Non-Error guard fix:
JavaScript allows any value to be thrown, so a catch block's `e` is not
guaranteed to be an Error instance. All three QA fs catch blocks now use
optional chaining (e?.code, e?.message) and fall back to String(e) for the
message field, preventing secondary TypeErrors during error logging.
All 60 tests pass; node --check clean.
https://claude.ai/code/session_01WpEqAiDbpqnBywMjYXJHf4
Merge conflicts (5 hunks in index.js): - Keep HEAD QA_FS_SUPPRESS without EEXIST (correct: recursive mkdirSync never throws EEXIST for dirs, so EEXIST means misconfiguration) - Keep HEAD optional-chaining (e?.code / e?.message ?? String(e)) throughout Review comment — centralize QA_FS_SUPPRESS: - Added authoritative JSDoc explaining the set is the single source of suppression semantics for all QA fs helpers; adding a code here propagates to every caller automatically Review comment — stack trace behind debug flag: - All QA logEvent warn calls now spread stack: e?.stack when LOG_LEVEL=debug Review comment — independent artifact writes in ensureQaArtifacts: - Extracted tryWriteQaJson() helper: each artifact write is now guarded independently so one serialization/path failure cannot block the others - ensureQaArtifacts() restructured to call tryWriteQaJson per file Review comment — runewager.service hardening: - ProtectHome=false → ProtectHome=true; added /root/.npm and /root/.cache to ReadWritePaths so Node/npm caches work without blanket /root access - UMask=0022 → UMask=0027 (files 640, dirs 750): blocks world-readable artifacts while keeping group-read for log-tailing utilities https://claude.ai/code/session_01WpEqAiDbpqnBywMjYXJHf4
runewager.service: - UMask=0022 → UMask=0077; files now default to 600, dirs to 700 - Updated comment documenting per-call mode strategy and noting that systemd StandardOutput/StandardError logs are opened by systemd (not the process) and thus unaffected by UMask; group access via usermod -aG or setfacl is the documented mechanism for those files index.js — explicit mode on every writeFileSync / appendFileSync: - writeFileAtomic (temp file): mode 0o600 — sensitive data (runtime-state.json) - adminLog → adminEventsLogFile: mode 0o640 — log file, group-readable - appendBonusAdminLog → bonusAdminLogFile: mode 0o640 — log file, group-readable - writeQaLog daily log files: mode 0o640 — observability, group-readable - persistQaProviderState → qaProviderStateFile: mode 0o600 — private state - tryWriteQaJson → QA artifact files: mode 0o600 — private artifacts - sshv editor save → session.editorMode.filePath: mode 0o600 — private draft https://claude.ai/code/session_01WpEqAiDbpqnBywMjYXJHf4
…k loops Root cause: step 11 "God-Mode Heal" duplicated the same wait_for_health (20 attempts × 2s = 40s) + systemctl restart block FIVE times back-to-back, totalling up to ~3.5 minutes of wasted waiting even on a clean boot. Changes: - Extracted `_kill_port_squatters()` helper (lsof-first, SIGTERM→SIGKILL) - Step 9c: two-pass aggressive port clear with xargs SIGKILL fallback runs BEFORE systemd restart so the service never races a squatter - Step 11: replaced 5 duplicated heal blocks with a single 30s health probe (15×2s), one optional auto-recovery restart, and a final 20s probe (10×2s) - Telegram notification uses read_env_value() (no raw grep), log capped at 3.5k chars Result: cold-start completes in ~15-35s instead of 3-5 minutes.
… block - xargs -r portability: replaced with explicit empty-guard so BSD xargs works - _REPORT arithmetic expansion bug: $(( ... )) → $( ( ... ) ) — commands now actually execute and log content is captured correctly - _REPORT now wired into Telegram notification text payload - SYSTEMD_ACTIVE moved to after auto-recovery path so summary reflects post-heal state, not pre-restart stale value
|
CodeAnt AI is reviewing your PR. Thanks for using CodeAnt! 🎉We're free for open-source projects. if you're enjoying it, help us grow by sharing. Share on X · |
Reviewer's GuideHarden pre-/post-start health and port conflict handling in prod-run.sh, simplify and centralize QA filesystem error handling and permissions in index.js, and tighten file modes for various logs and artifacts. Sequence diagram for aggressive pre-start port conflict clearingsequenceDiagram
participant ProdRunner as prod_run_sh
participant PortCheck as is_port_listening
participant Killer as _kill_port_squatters
participant Lsof as lsof_or_port_listener_pids
participant OS as OS_processes
ProdRunner->>ProdRunner: PORT_PRESTART = read_env_value PORT (default 3000)
ProdRunner->>PortCheck: is_port_listening(PORT_PRESTART)
alt port_in_use
ProdRunner->>ProdRunner: say Port in use — aggressively clearing
ProdRunner->>Killer: _kill_port_squatters(PORT_PRESTART, PID)
Killer->>Lsof: list listening PIDs on PORT_PRESTART
Lsof-->>Killer: PID list
loop for_each_pid
Killer->>OS: kill -TERM pid
OS-->>Killer: process may still exist
Killer->>OS: sleep 1 then kill -KILL pid if still alive
end
ProdRunner->>ProdRunner: sleep 1
ProdRunner->>PortCheck: is_port_listening(PORT_PRESTART)
alt still_in_use
ProdRunner->>ProdRunner: warn Port still occupied — forcing SIGKILL
ProdRunner->>Lsof: lsof -t -iTCP:PORT_PRESTART -sTCP:LISTEN
Lsof-->>ProdRunner: stray PID list
ProdRunner->>OS: xargs kill -9 stray_pids
ProdRunner->>ProdRunner: sleep 1
end
end
Sequence diagram for post-start health check and single auto-recoverysequenceDiagram
participant ProdRunner as prod_run_sh
participant HealthUrl as resolve_health_url
participant WaitHealth as wait_for_health
participant PortCheck as is_port_listening
participant Killer as _kill_port_squatters
participant Systemd as systemctl
participant NodeProc as node_index_js
participant Telegram as telegram_api
ProdRunner->>ProdRunner: say Running post-start health check
ProdRunner->>HealthUrl: resolve_health_url()
HealthUrl-->>ProdRunner: HEALTH_URL
ProdRunner->>ProdRunner: derive HEALTH_PORT from HEALTH_URL
ProdRunner->>ProdRunner: HEALTH_STATUS = unhealthy, PORT_STATUS = not-listening
ProdRunner->>WaitHealth: wait_for_health(HEALTH_URL, 15, 2)
alt health_success
WaitHealth-->>ProdRunner: success
ProdRunner->>ProdRunner: HEALTH_STATUS = healthy
else health_fail
WaitHealth-->>ProdRunner: failure
ProdRunner->>PortCheck: is_port_listening(HEALTH_PORT)
alt port_listening
PortCheck-->>ProdRunner: true
ProdRunner->>ProdRunner: warn Port still squatted — clear + one restart
ProdRunner->>Killer: _kill_port_squatters(HEALTH_PORT, PID)
Killer-->>ProdRunner: squatters cleared
ProdRunner->>ProdRunner: sleep 1
alt systemd_available
ProdRunner->>Systemd: systemctl restart APP_NAME.service
Systemd-->>ProdRunner: restart triggered
else no_systemd
ProdRunner->>NodeProc: kill PID (if set)
ProdRunner->>NodeProc: nohup node index.js
end
ProdRunner->>ProdRunner: sleep 3
ProdRunner->>ProdRunner: PID = get_bot_pid()
end
ProdRunner->>WaitHealth: wait_for_health(HEALTH_URL, 10, 2)
alt final_health_success
WaitHealth-->>ProdRunner: success
ProdRunner->>ProdRunner: HEALTH_STATUS = healthy
else final_health_fail
WaitHealth-->>ProdRunner: failure
ProdRunner->>ProdRunner: HEALTH_STATUS remains unhealthy
end
end
ProdRunner->>PortCheck: is_port_listening(HEALTH_PORT)
alt listening_now
PortCheck-->>ProdRunner: true
ProdRunner->>ProdRunner: PORT_STATUS = listening
else still_not_listening
PortCheck-->>ProdRunner: false
ProdRunner->>ProdRunner: PORT_STATUS = not-listening
end
ProdRunner->>Systemd: systemctl is-active APP_NAME.service
Systemd-->>ProdRunner: SYSTEMD_ACTIVE
ProdRunner->>ProdRunner: _ADMIN_IDS = read_env_value ADMIN_IDS
ProdRunner->>ProdRunner: _BOT_TOKEN = read_env_value TELEGRAM_BOT_TOKEN
alt telegram_configured
ProdRunner->>ProdRunner: build _REPORT from MAIN_LOG and ERROR_LOG
loop for_each_admin
ProdRunner->>Telegram: sendMessage(bot_token, admin_id, summary + _REPORT)
Telegram-->>ProdRunner: 200 OK (ignored)
end
end
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
CodeAnt AI finished reviewing your PR. |
Summary by Sourcery
Aggressively clear port conflicts before and after restart, simplify post-start health checks and Telegram reporting, and harden filesystem operations and permissions for logs, QA artifacts, and editor saves.
Enhancements: