Skip to content

Claude/cpu guard script dn d8u - #132

Merged
gamblecodezcom merged 9 commits into
mainfrom
claude/cpu-guard-script-DnD8u
Mar 4, 2026
Merged

Claude/cpu guard script dn d8u#132
gamblecodezcom merged 9 commits into
mainfrom
claude/cpu-guard-script-DnD8u

Conversation

@gamblecodezcom

@gamblecodezcom gamblecodezcom commented Mar 4, 2026

Copy link
Copy Markdown
Owner

User description

Summary by Sourcery

Improve deployment robustness and QA observability by hardening port conflict handling, simplifying the post-start health/notification flow, and tightening file permissions and error handling for logs and QA artifacts.

Enhancements:

  • Add aggressive pre-start port conflict clearing with fallback SIGKILL to prevent systemd restarts from racing with lingering listeners.
  • Refactor the post-start health check into a single-pass flow with bounded auto-recovery and streamlined Telegram reporting that includes concise log snippets.
  • Harden filesystem interactions for logs and QA artifacts by explicitly setting restrictive file modes, centralizing QA filesystem error suppression, and improving diagnostic logging for unexpected QA errors.
  • Ensure editor save operations write files with secure permissions to reduce exposure of sensitive content.

CodeAnt-AI Description

Harden startup, observability, and file safety; avoid crashes from QA writes; aggressively clear port conflicts and simplify health reporting

What Changed

  • Pre-start and post-start scripts now forcibly clear any process holding the bot port (TERM then KILL) so systemd won't repeatedly restart due to stray listeners; a single automatic recovery restart is attempted if health fails.
  • Post-start health check runs as a single bounded pass with one retry after clearing port squatters, and sends a concise Telegram deploy status to configured admins (shorter log snippet, limited size).
  • File writes for logs, QA artifacts, runtime state, and editor saves are written with restrictive file modes (600/640) to limit access and reduce accidental exposure.
  • QA artifact writes are non-fatal and each file write is guarded independently; expected permission errors (EACCES/EPERM/EROFS) are silently ignored while unexpected errors are logged with more diagnostic detail when debug logging is enabled.
  • systemd unit tightened: ProtectHome is enabled and UMask set to produce 600/700 defaults; explicit ReadWritePaths added for bot data, logs, QA, and npm cache locations.

Impact

✅ Fewer startup restarts due to port conflicts
✅ Clearer deploy health status for admins
✅ Lower risk of sensitive file exposure

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

Summary by CodeRabbit

Release Notes

  • New Features

    • Telegram notifications now inform administrators of deployment completion and service health status.
  • Bug Fixes

    • Enhanced port conflict resolution with more aggressive cleanup during service restarts.
    • Streamlined health verification with improved recovery procedures.
  • Chores

    • Hardened filesystem permissions and security sandboxing.
    • Improved error handling and diagnostic reporting.

claude added 9 commits March 4, 2026 13:00
…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.
@codeant-ai

codeant-ai Bot commented Mar 4, 2026

Copy link
Copy Markdown

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 ·
Reddit ·
LinkedIn

@sourcery-ai

sourcery-ai Bot commented Mar 4, 2026

Copy link
Copy Markdown

Reviewer's Guide

Strengthens restart reliability and post-deploy health checks, tightens filesystem error handling, and standardizes secure file permissions for logs and QA artifacts.

Sequence diagram for aggressive port clearing and single-pass post-start health check

sequenceDiagram
    participant prod_run_sh
    participant lsof as lsof_or_port_listener_pids
    participant port_squatter as port_squatter_processes
    participant systemd as systemd_service
    participant node_app as node_app
    participant health as health_endpoint
    participant telegram as telegram_api

    %% Pre-start aggressive port clear
    prod_run_sh->>prod_run_sh: read_env_value PORT
    prod_run_sh->>prod_run_sh: is_port_listening PORT_PRESTART
    alt port_in_use
        prod_run_sh->>lsof: list PIDs listening on PORT_PRESTART
        lsof-->>prod_run_sh: PIDs
        loop each_pid
            prod_run_sh->>port_squatter: kill -TERM pid
            prod_run_sh->>port_squatter: if still alive kill -KILL pid
        end
        prod_run_sh->>prod_run_sh: sleep 1
        prod_run_sh->>prod_run_sh: is_port_listening PORT_PRESTART
        alt still_in_use
            prod_run_sh->>lsof: lsof -t -iTCP:PORT_PRESTART
            lsof-->>prod_run_sh: remaining PIDs
            prod_run_sh->>port_squatter: kill -9 remaining PIDs
            prod_run_sh->>prod_run_sh: sleep 1
        end
    end

    %% (Service start happens via systemd earlier in script)
    prod_run_sh->>systemd: systemctl restart APP_NAME.service (if available)
    systemd->>node_app: start

    %% Post-start health check single pass
    prod_run_sh->>prod_run_sh: HEALTH_URL = resolve_health_url()
    prod_run_sh->>prod_run_sh: derive HEALTH_PORT
    prod_run_sh->>systemd: systemctl is-active APP_NAME.service
    systemd-->>prod_run_sh: SYSTEMD_ACTIVE
    prod_run_sh->>health: wait_for_health(HEALTH_URL, 15, 2)
    alt health_ok_initial
        health-->>prod_run_sh: healthy
        prod_run_sh->>prod_run_sh: HEALTH_STATUS = healthy
    else health_failed_initial
        health-->>prod_run_sh: unhealthy
        prod_run_sh->>prod_run_sh: HEALTH_STATUS = unhealthy
        prod_run_sh->>prod_run_sh: is_port_listening HEALTH_PORT
        alt port_still_squatted
            prod_run_sh->>port_squatter: _kill_port_squatters(HEALTH_PORT, PID)
            prod_run_sh->>prod_run_sh: sleep 1
            alt systemd_available
                prod_run_sh->>systemd: systemctl restart APP_NAME.service
            else no_systemd
                prod_run_sh->>node_app: kill PID (if set)
                prod_run_sh->>node_app: nohup node index.js &
            end
            prod_run_sh->>prod_run_sh: sleep 3
            prod_run_sh->>prod_run_sh: PID = get_bot_pid()
        end
        prod_run_sh->>health: wait_for_health(HEALTH_URL, 10, 2)
        alt health_ok_after_recovery
            health-->>prod_run_sh: healthy
            prod_run_sh->>prod_run_sh: HEALTH_STATUS = healthy
        else still_unhealthy
            health-->>prod_run_sh: unhealthy
            prod_run_sh->>prod_run_sh: HEALTH_STATUS = unhealthy
        end
    end

    %% Final port status and Telegram report
    prod_run_sh->>prod_run_sh: is_port_listening HEALTH_PORT
    alt listening
        prod_run_sh->>prod_run_sh: PORT_STATUS = listening
    else not_listening
        prod_run_sh->>prod_run_sh: PORT_STATUS = not-listening
    end

    prod_run_sh->>prod_run_sh: _ADMIN_IDS = read_env_value ADMIN_IDS
    prod_run_sh->>prod_run_sh: _BOT_TOKEN = read_env_value TELEGRAM_BOT_TOKEN
    alt has_telegram_config
        prod_run_sh->>prod_run_sh: _REPORT = last log lines (bounded)
        loop each_admin_id
            prod_run_sh->>telegram: sendMessage(health, port, PID)
            telegram-->>prod_run_sh: 200 OK (ignored)
        end
    else no_telegram_config
        prod_run_sh->>prod_run_sh: skip Telegram notification
    end
Loading

Flow diagram for QA filesystem helpers and artifact persistence

flowchart TD
    A[start ensureQaArtifacts] --> B[ensureQaDirs]
    B --> C{mkdirSync for each QA dir}
    C -->|success| D[dirs ensured]
    C -->|error e| E{QA_FS_SUPPRESS has e.code?}
    E -->|yes| F[ignore permission error]
    E -->|no| G[logEvent warn ensureQaDirs unexpected error]
    F --> D
    G --> D

    D --> H[tryWriteQaJson qaRepoInfoFile]
    H --> I{fs.writeFileSync repo_info}
    I -->|success| J[repo_info written mode 0600]
    I -->|error e| K{QA_FS_SUPPRESS has e.code?}
    K -->|yes| L[ignore suppressed error]
    K -->|no| M[logEvent warn ensureQaArtifacts repo_info unexpected error]
    L --> N[next artifact]
    M --> N

    N --> O[tryWriteQaJson qaCapabilitiesFile]
    O --> P{fs.writeFileSync capabilities}
    P -->|success| Q[capabilities written mode 0600]
    P -->|error e| R{QA_FS_SUPPRESS has e.code?}
    R -->|yes| S[ignore suppressed error]
    R -->|no| T[logEvent warn ensureQaArtifacts capabilities unexpected error]
    S --> U[persistQaProviderState]
    T --> U

    U --> V[ensureQaDirs]
    V --> W{mkdirSync for each QA dir}
    W -->|success or suppressed| X[dirs ensured for state]
    W -->|unexpected error| Y[logEvent warn ensureQaDirs unexpected error]
    X --> Z{fs.writeFileSync qaProviderStateFile}
    Z -->|success| AA[state written mode 0600]
    Z -->|error e| AB{QA_FS_SUPPRESS has e.code?}
    AB -->|yes| AC[return silently nonfatal]
    AB -->|no| AD[logEvent warn persistQaProviderState unexpected error]

    J --> N
    Q --> U

    AC --> AE[end ensureQaArtifacts]
    AD --> AE
    AA --> AE
Loading

File-Level Changes

Change Details Files
Aggressively clear port conflicts before restart and reuse helper during post-start recovery.
  • Introduce _kill_port_squatters helper using lsof (or port_listener_pids) to find listeners, send TERM then KILL, and optionally skip the current PID.
  • Replace free_port_if_conflicted pre-start usage with _kill_port_squatters and add a second pass that SIGKILLs any remaining listeners via lsof.
  • During post-start health recovery, call _kill_port_squatters on the health port before performing a single guarded restart path.
prod-run.sh
Simplify and harden post-start health check flow into a single-pass probe plus one optional recovery attempt.
  • Remove prior multi-phase God-mode heal logic, duplicate health checks, and inline port-free logic, replacing with a linear: wait_for_health, optional auto-recovery, then a final probe.
  • Normalize HEALTH_STATUS/PORT_STATUS initialization and final setting, and rely on resolve_health_url and is_port_listening for a single canonical health URL/port computation.
  • Ensure systemd restart fallback path uses existing helpers and avoids redundant restarts or multiple nested health loops.
prod-run.sh
Refine Telegram notification after deploy to be simpler, bounded, and based on final health state.
  • Switch to using read_env_value for ADMIN_IDS and TELEGRAM_BOT_TOKEN instead of direct .env greps.
  • Limit log excerpt size (tail 30 main / 20 error, truncated to ~3.5k chars) and send a concise status line summarizing health, port, and PID.
  • Remove earlier, more verbose markdown log-dump report and align notification timing with the simplified health check.
prod-run.sh
Harden file writes for logs, QA artifacts, and temp files with explicit permissions and options objects.
  • Set bonus/admin and QA log appendFileSync calls to explicitly use encoding 'utf8' and mode 0o640.
  • Force sensitive JSON artifacts and temp files (writeFileAtomic, QA provider state, SSH editor saves) to mode 0o600.
  • Update appendFileSync/writeFileSync invocation style to use options objects where appropriate for consistency and security.
index.js
Centralize QA filesystem error suppression and make QA artifact writes more robust and observable.
  • Redefine QA_FS_SUPPRESS with documentation, dropping EEXIST so directory/file collisions are surfaced instead of silently ignored.
  • Guard all QA filesystem error checks with optional chaining, improve logEvent context (include code, error message, and optional stack under LOG_LEVEL=debug).
  • Introduce tryWriteQaJson helper that applies QA_FS_SUPPRESS semantics per artifact, then refactor ensureQaArtifacts to call it for repo info and capabilities while keeping provider state persisted via persistQaProviderState.
index.js

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@gamblecodezcom
gamblecodezcom merged commit 31f72b3 into main Mar 4, 2026
4 of 5 checks passed
@gamblecodezcom
gamblecodezcom deleted the claude/cpu-guard-script-DnD8u branch March 4, 2026 14:14
@coderabbitai

coderabbitai Bot commented Mar 4, 2026

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 259c7c1b-22a7-4957-93c9-c6cd01cc5483

📥 Commits

Reviewing files that changed from the base of the PR and between 328d681 and 5e08dee.

📒 Files selected for processing (3)
  • index.js
  • prod-run.sh
  • runewager.service

📝 Walkthrough

Walkthrough

The changes tighten file permissions (0o600, 0o640), introduce centralized QA artifact writing helpers (tryWriteQaJson, ensureQaArtifacts), adjust error suppression logic in QA_FS_SUPPRESS by removing EEXIST, replace multi-stage health checks with simplified single-pass logic and Telegram deployment notifications, and harden systemd service security through ProtectHome and UMask changes.

Changes

Cohort / File(s) Summary
Filesystem permissions and QA artifact handling
index.js
Explicit file mode permissions (0o600, 0o640) added to log appends and write operations; introduced tryWriteQaJson() helper with centralized error suppression; refactored ensureQaArtifacts() to persist QA repo info, capabilities, and provider state; updated QA_FS_SUPPRESS set to exclude EEXIST for surfacing misconfigurations; enhanced error reporting with optional chaining and debug-mode stack traces.
Port conflict resolution and health check automation
prod-run.sh
Replaced light-weight port freeing with aggressive _kill_port_squatters() function using lsof or port listener PIDs; added SIGTERM and SIGKILL escalation for occupying processes; simplified multi-stage health checks into single-pass logic with optional one-shot recovery via systemd; introduced Telegram reporting for deployment completion status, health status, and port info via environment-driven _ADMIN_IDS and _BOT_TOKEN.
Systemd security hardening
runewager.service
ProtectHome changed from false to true for filesystem sandboxing; UMask tightened from 0022 to 0077; ReadWritePaths expanded to include /root/.npm and /root/.cache alongside existing application paths.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Possibly related PRs

  • PR #131: Directly overlaps on QA filesystem handling (QA_FS_SUPPRESS constant and ensureQaArtifacts logic implementation).
  • PR #123: Modifies the same deployment artifacts (prod-run.sh and runewager.service) with overlapping systemd and startup behavior changes.
  • PR #126: Addresses port-conflict handling in prod-run.sh with hardened pkill/port-related commands and enhanced log path handling.

Suggested labels

codex, size:XXL

Poem

Wiggles nose knowingly 🐰

Mode 0o600 fortified this day,
QA artifacts bundled away,
Port squatters evicted with glee,
Health checks swift as can be,
Telegram bells ring: deployment's OK! 📲

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch claude/cpu-guard-script-DnD8u

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codeant-ai

codeant-ai Bot commented Mar 4, 2026

Copy link
Copy Markdown

Sequence Diagram

The PR hardens pre-start port clearing, simplifies the post-start health probe into a single-pass auto-recovery (clear squatters + one restart), and reports a concise Telegram summary. The diagram shows the core deploy script interactions during restart and health verification.

sequenceDiagram
    participant Deployer as prod-run.sh
    participant Port as PortSquatter
    participant Service as Systemd/Node
    participant Health as HealthEndpoint
    participant Telegram as Telegram API

    Deployer->>Port: Detect listening on configured port
    Deployer->>Port: Terminate listeners (TERM then KILL fallback)
    Deployer->>Service: Restart service (systemctl or nohup)
    Service->>Health: Serve health endpoint
    Deployer->>Health: Probe health (bounded single-pass)
    alt healthy
        Deployer->>Telegram: Send concise "Deploy complete: OK" (trimmed logs)
    else unhealthy
        Deployer->>Port: If still occupied, clear squatters and restart once
        Deployer->>Service: Restart service (one auto-recovery)
        Deployer->>Health: Final health probe
        Deployer->>Telegram: Send concise "Deploy complete: FAILED" (trimmed logs)
    end
Loading

Generated by CodeAnt AI

@codeant-ai codeant-ai Bot added the size:L This PR changes 100-499 lines, ignoring generated files label Mar 4, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • In the post-start Telegram notification block, _REPORT is constructed but never used; either remove the unused variable or incorporate the log excerpt into the message payload to avoid dead code.
  • The xargs -r usage in the _kill_port_squatters follow-up SIGKILL path may not be portable to all environments (e.g., some BSD xargs do not support -r); consider a more portable pattern such as guarding xargs with an if or using xargs kill -9 with an input check.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In the post-start Telegram notification block, `_REPORT` is constructed but never used; either remove the unused variable or incorporate the log excerpt into the message payload to avoid dead code.
- The `xargs -r` usage in the `_kill_port_squatters` follow-up SIGKILL path may not be portable to all environments (e.g., some BSD `xargs` do not support `-r`); consider a more portable pattern such as guarding `xargs` with an `if` or using `xargs kill -9` with an input check.

## Individual Comments

### Comment 1
<location path="prod-run.sh" line_range="523-531" />
<code_context>
-    done
-fi
-
-SYSTEMD_ACTIVE="$(systemctl is-active "${APP_NAME}.service" 2>/dev/null || echo inactive)"
-HEALTH_URL="$(resolve_health_url)"
-HEALTH_PORT="${HEALTH_URL#*://127.0.0.1:}"
-HEALTH_PORT="${HEALTH_PORT%%/*}"
-
-if wait_for_health "$HEALTH_URL" 20 2; then
-    HEALTH_STATUS="healthy"
-else
-    HEALTH_STATUS="unhealthy"
-fi
-
</code_context>
<issue_to_address>
**issue (bug_risk):** SYSTEMD_ACTIVE can become stale after the auto-recovery restart branch.

Because SYSTEMD_ACTIVE is set only once and reused in the final summary, the reported status may not match the actual state after `systemctl restart` runs in the auto-recovery path. Please either move the `systemctl is-active` call to just before the final summary, or recompute SYSTEMD_ACTIVE after the restart so the final line reflects the post-heal state.
</issue_to_address>

### Comment 2
<location path="prod-run.sh" line_range="550-551" />
<code_context>
+_ADMIN_IDS="$(read_env_value ADMIN_IDS || true)"
+_BOT_TOKEN="$(read_env_value TELEGRAM_BOT_TOKEN || true)"
+if [[ -n "$_BOT_TOKEN" && -n "$_ADMIN_IDS" ]]; then
+    _REPORT="$(( tail -n 30 "$MAIN_LOG"; tail -n 20 "$ERROR_LOG" ) 2>/dev/null \
+        | sed 's/"/\\"/g' | head -c 3500)"
+    for _AID in ${_ADMIN_IDS//,/ }; do
+        curl -s -X POST "https://api.telegram.org/bot${_BOT_TOKEN}/sendMessage" \
</code_context>
<issue_to_address>
**issue:** _REPORT is computed but never used in the Telegram notification.

`_REPORT` is built from the log tails but never used in the `curl` request. Please either remove this construction or wire it into the Telegram payload (or a separate message) if the intent is to send recent logs to admins.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread prod-run.sh
Comment on lines 523 to 531
HEALTH_STATUS="healthy"
else
HEALTH_STATUS="unhealthy"

# Auto-recover from port conflicts, then retry health once.
# One auto-recovery: clear any squatter and restart once
if is_port_listening "$HEALTH_PORT"; then
free_port_if_conflicted "$HEALTH_PORT" "$PID" || true
warn "Port $HEALTH_PORT still squatted after start — forcing clear + one restart"
_kill_port_squatters "$HEALTH_PORT" "${PID:-}"
sleep 1
if command -v systemctl >/dev/null 2>&1 && [[ -f "$SERVICE_FILE" ]]; then
systemctl restart "${APP_NAME}.service" 2>/dev/null || true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): SYSTEMD_ACTIVE can become stale after the auto-recovery restart branch.

Because SYSTEMD_ACTIVE is set only once and reused in the final summary, the reported status may not match the actual state after systemctl restart runs in the auto-recovery path. Please either move the systemctl is-active call to just before the final summary, or recompute SYSTEMD_ACTIVE after the restart so the final line reflects the post-heal state.

Comment thread prod-run.sh
Comment on lines +550 to +551
_REPORT="$(( tail -n 30 "$MAIN_LOG"; tail -n 20 "$ERROR_LOG" ) 2>/dev/null \
| sed 's/"/\\"/g' | head -c 3500)"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: _REPORT is computed but never used in the Telegram notification.

_REPORT is built from the log tails but never used in the curl request. Please either remove this construction or wire it into the Telegram payload (or a separate message) if the intent is to send recent logs to admins.

@codeant-ai

codeant-ai Bot commented Mar 4, 2026

Copy link
Copy Markdown

Nitpicks 🔍

🔒 No security issues identified
⚡ Recommended areas for review

  • Privilege surface
    The new ReadWritePaths explicitly grants write access to /root/.npm and /root/.cache to accommodate npm caches while ProtectHome=true is set. This increases the filesystem surface the service can write to; verify the minimal necessary paths and consider running the service as a non-root user to reduce blast radius.

  • Port-kill portability
    The aggressive SIGKILL pass uses xargs -r kill -9 which is a GNU extension (-r) not present in BSD xargs; combined with piping lsof output directly to xargs this may fail or behave differently on non-GNU systems. Also using a one-shot pipeline to mass-KILL can inadvertently kill unrelated PIDs if parsing isn't robust.

  • Broken Capture
    The combined log capture for _REPORT uses arithmetic expansion $(( ... )) instead of command substitution $( ... ), and the captured _REPORT is never included in the outgoing Telegram message — the current code will not produce the intended report and may break the assignment/expand. This should be fixed so the report is produced, size-limited, safely escaped, and actually sent (or dropped if intentionally unused).

Comment thread prod-run.sh
Comment on lines +550 to +556
_REPORT="$(( tail -n 30 "$MAIN_LOG"; tail -n 20 "$ERROR_LOG" ) 2>/dev/null \
| sed 's/"/\\"/g' | head -c 3500)"
for _AID in ${_ADMIN_IDS//,/ }; do
curl -s -X POST "https://api.telegram.org/bot${_BOT_TOKEN}/sendMessage" \
-d chat_id="$_AID" \
-d parse_mode="Markdown" \
-d text="Deploy complete: Health $( [[ "$HEALTH_STATUS" == healthy ]] && echo OK || echo FAILED) Port: $HEALTH_PORT PID: ${PID:-none}" >/dev/null 2>&1 || true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The Telegram deploy notification block attempts to build a log snippet using arithmetic expansion ($(( ... ))) instead of command substitution ($( ... )), so the tail commands are never executed as commands, and the resulting _REPORT content is meaningless and never included in the message body; switch to proper command substitution and append _REPORT to the text payload so admins actually receive the intended log excerpt. [logic error]

Severity Level: Critical 🚨
- ❌ prod-run.sh exits when Telegram alerts are configured.
- ⚠️ Telegram deploy messages lack intended log context. 
Suggested change
_REPORT="$(( tail -n 30 "$MAIN_LOG"; tail -n 20 "$ERROR_LOG" ) 2>/dev/null \
| sed 's/"/\\"/g' | head -c 3500)"
for _AID in ${_ADMIN_IDS//,/ }; do
curl -s -X POST "https://api.telegram.org/bot${_BOT_TOKEN}/sendMessage" \
-d chat_id="$_AID" \
-d parse_mode="Markdown" \
-d text="Deploy complete: Health $( [[ "$HEALTH_STATUS" == healthy ]] && echo OK || echo FAILED) Port: $HEALTH_PORT PID: ${PID:-none}" >/dev/null 2>&1 || true
_REPORT="$( ( tail -n 30 "$MAIN_LOG"; tail -n 20 "$ERROR_LOG" ) 2>/dev/null \
| sed 's/"/\\"/g' | head -c 3500)"
for _AID in ${_ADMIN_IDS//,/ }; do
curl -s -X POST "https://api.telegram.org/bot${_BOT_TOKEN}/sendMessage" \
-d chat_id="$_AID" \
-d parse_mode="Markdown" \
-d text="Deploy complete: Health $( [[ "$HEALTH_STATUS" == healthy ]] && echo OK || echo FAILED) Port: $HEALTH_PORT PID: ${PID:-none}\n\n${_REPORT}" >/dev/null 2>&1 || true
done
Steps of Reproduction ✅
1. Ensure Telegram notifications are configured by setting `TELEGRAM_BOT_TOKEN` and
`ADMIN_IDS` in `.env`, which are read by `read_env_value` at `prod-run.sh:547-548`.

2. Deploy using the documented entrypoint, e.g. run `npm run prod` (which invokes
`./prod-run.sh` per `package.json:14`) or execute `bash prod-run.sh` from the project
root.

3. After the bot is restarted and the health check passes/finalizes (logic at
`prod-run.sh:514-542`), execution reaches the Telegram admin notification block at
`prod-run.sh:546-557`.

4. With `set -euo pipefail` enabled at `prod-run.sh:9`, the line `_REPORT="$(( tail -n 30
"$MAIN_LOG"; tail -n 20 "$ERROR_LOG" ) 2>/dev/null \` at `prod-run.sh:550-551` is parsed
as arithmetic expansion: `tail`/`sed` tokens are not valid arithmetic, causing a Bash
"syntax error in expression" and immediate script exit before any `tail` commands run or
any `curl` notification is sent; even if `set -e` were relaxed, `_REPORT` would not
contain the intended log snippet and is never interpolated into the `text` field at
`prod-run.sh:556`, so admins would receive at most a bare status line without logs.
Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** prod-run.sh
**Line:** 550:556
**Comment:**
	*Logic Error: The Telegram deploy notification block attempts to build a log snippet using arithmetic expansion (`$(( ... ))`) instead of command substitution (`$( ... )`), so the `tail` commands are never executed as commands, and the resulting `_REPORT` content is meaningless and never included in the message body; switch to proper command substitution and append `_REPORT` to the `text` payload so admins actually receive the intended log excerpt.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
👍 | 👎

@codeant-ai

codeant-ai Bot commented Mar 4, 2026

Copy link
Copy Markdown

CodeAnt AI finished reviewing your PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants