Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
c3ce323
feat(v3.0): full production upgrade — menu lifecycle, pagination, sec…
claude Feb 28, 2026
1c1d19c
Merge remote-tracking branch 'origin/main' into claude/plan-v3-deploy…
claude Feb 28, 2026
92e93e6
fix(codex-recovery): restore missing codex branch changes overwritten…
claude Feb 28, 2026
3d4befe
fix(pr110): address all reviewer comments from PR #110
claude Feb 28, 2026
41c2083
feat(vnext): Helpful Tooltips overhaul, giveaway v3.0+ upgrades
claude Feb 28, 2026
c279f5f
feat(v3.1): group command guard, onboarding progress bar, admin group…
claude Feb 28, 2026
81ff2af
fix(pr112+audit): address all 6 PR review comments and 4 audit duplic…
claude Feb 28, 2026
86a6e1d
merge: sync with origin/main; keep PR #112 fixes over regressed versions
claude Feb 28, 2026
b6e211c
feat(scripts): auto git-pull + tooltip-refresh + port-free before eve…
claude Feb 28, 2026
5c27692
fix(pr113): resolve all PR #113 review comments
claude Feb 28, 2026
efe20d6
docs: add per-feature documentation system and central index
claude Mar 1, 2026
6ab6734
merge: sync with origin/main (PRs #112-114); keep SIGTERM→SIGKILL and…
claude Mar 1, 2026
49efa74
fix(pr115): resolve 3 review comments + implement T-01/T-02/T-03/T-15
claude Mar 1, 2026
ee1184c
fix(pr115): tooltip skip guard, shared port helper, gated auto-update
claude Mar 1, 2026
ad306be
fix(pr115): address 4 reviewer hardening findings
claude Mar 1, 2026
c865189
fix(pr115): 3 nitpick hardening fixes
claude Mar 1, 2026
f0f53aa
fix(pr115): temp-file trap + non-destructive git update path
claude Mar 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ MINI_APP_CLAIM_URL=https://t.me/RuneWager_bot/claim
MINI_APP_PLAY_URL=https://t.me/RuneWager_bot/Play
MINI_APP_PROFILE_URL=https://t.me/RuneWager_bot/profile
PORT=3000
# RUNEWAGER_AUTO_UPDATE: Set to 1 to auto-pull origin/main on start/restart.
# Default: 1 in prod (start.sh), 0 in dev (dev-run.sh).
# Set to 0 on local/staging to avoid overwriting uncommitted changes.
RUNEWAGER_AUTO_UPDATE=1
# RUNEWAGER_FORCE_RESET: Set to 1 to use git reset --hard instead of merge --ff-only.
# Destructive: discards all local uncommitted changes. Default: 0 (disabled).
RUNEWAGER_FORCE_RESET=0
PROMO_ENTRY_IMAGE_URL=https://raw.githubusercontent.com/gamblecodezcom/Runewager/main/images/promo_entry.png
RW_DISCORD_JOIN=https://discord.gg/runewagers
RW_DISCORD_LINK=https://discord.com/channels/1100486422395355197/1249181934811349052
Expand Down
33 changes: 33 additions & 0 deletions RUNEWAGER_FUNCTIONALITY_MAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -402,4 +402,37 @@ Mandatory rules for any AI agent touching this repo:
- 2026-02-27: Added QA tester scaffolding (`qa/context/bot_capabilities.json`, `qa/context/repo_info.json`, `qa/state/provider_status.json`, `qa/README_QA.md`), with runtime refresh via `/qa_*` commands and 10-minute provider cooldown reset.
- 2026-02-27: Hardened SSHV Run prompt flow so admin text in private DM executes against active SSHV sessions if pending state desynchronizes.
- 2026-02-28: v3.1 — added group command guard middleware (`GROUP_PASSTHROUGH_COMMANDS` + `bot.use` interceptor); added `onboardingProgressBar()` and progress header on each onboarding step prompt (auto-deletes after 8s); added one-time onboarding completion card (tracked via `user.onboarding.completionCardShown`); added `🔗 Group Linking` to Admin System Tools keyboard (`admin_sys_group_linking` callback with back-to-system-tools navigation).
- 2026-03-01: Created `docs/` feature documentation system — 15 per-feature `.md` files, central `docs/INDEX.md` with full callback + pending-action cross-reference, and `docs/TODO_FUNCTIONALITY_UPGRADE.md` tracking 14 open upgrade/stale-menu items. Future Claude sessions must consult `docs/INDEX.md` first, then the relevant feature `.md`, before reading `index.js`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Resolve directive conflict: map-first vs docs-index-first.

Line 405 says sessions must consult docs/INDEX.md first, which conflicts with this file’s own map-first contract (Line 396-399). Keep one authoritative ordering to prevent process drift.

Suggested patch
-- 2026-03-01: Created `docs/` feature documentation system — 15 per-feature `.md` files, central `docs/INDEX.md` with full callback + pending-action cross-reference, and `docs/TODO_FUNCTIONALITY_UPGRADE.md` tracking 14 open upgrade/stale-menu items. Future Claude sessions must consult `docs/INDEX.md` first, then the relevant feature `.md`, before reading `index.js`.
+- 2026-03-01: Created `docs/` feature documentation system — 15 per-feature `.md` files, central `docs/INDEX.md` with full callback + pending-action cross-reference, and `docs/TODO_FUNCTIONALITY_UPGRADE.md` tracking 14 open upgrade/stale-menu items. Future Claude sessions should read this map first, then `docs/INDEX.md` and the relevant feature `.md`, before reading `index.js`.

Based on learnings: "Always treat RUNEWAGER_FUNCTIONALITY_MAP.md as the authoritative source of truth before and after code changes" and "Begin every coding session by reading RUNEWAGER_FUNCTIONALITY_MAP.md; end with full verification pass".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@RUNEWAGER_FUNCTIONALITY_MAP.md` at line 405, Update the conflicting guidance
so RUNEWAGER_FUNCTIONALITY_MAP.md remains authoritative: replace or revise the
sentence referencing `docs/INDEX.md` at the current Line 405 so it instructs
readers to consult RUNEWAGER_FUNCTIONALITY_MAP.md first (the "map-first"
contract referenced around Lines 396-399), then the `docs/INDEX.md` and
per-feature `.md` files as secondary resources; ensure the wording explicitly
enforces "map-first" startup and verification steps and remove any language that
mandates `docs/INDEX.md` as the initial source of truth.

- 2026-03-01: Phase implementation — resolved T-01/T-02/T-03/T-15 from TODO list. (1) Walkthrough: `sendWalkthroughStep()` upgraded with `clearOldMenus()`, Back disabled on step 1, Finish on last step, `walk_done` on last step returns to main menu. New doc: `16-walkthrough.md`. (2) Menu stacking: `clearOldMenus()` added to `sendOnboardingReferralPrompt`, `renderSshvConsole`, `renderGroupLinkingTools`, `tips_cmd_edit`, `tips_cmd_remove`. (3) Tooltip view: `tips_cmd_view` selector + `tip_view_{id}` handler with Prev/Next/Edit/Toggle/Delete/Back/AdminMenu — "👁 View Tooltip" button added to dashboard. (4) Broadcast failures: 500-item cap removed; all failures logged via `adminLog()`; `/broadcast_failed` shows chunks of 30 with overflow note; >20% failure rate triggers admin DM warnings. PR comments fixed: `add_tooltip.sh` array validation hardened; `docs/12-group-linking.md` entry-point callback corrected to `admin_sys_group_linking`; merge conflicts (PRs #112-114) resolved keeping SIGTERM→SIGKILL safety improvements. 60/60 tests pass.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix broadcast-failure retention contradiction in the authoritative map.

Line 406 says the 500-item cap was removed, but the earlier safety section says failure tracking caps exist to bound memory growth. Keep one consistent statement, or explicitly document current retention policy.

As per coding guidelines: "RUNEWAGER_FUNCTIONALITY_MAP.md: Always treat RUNEWAGER_FUNCTIONALITY_MAP.md as the authoritative source of truth before and after code changes".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@RUNEWAGER_FUNCTIONALITY_MAP.md` at line 406, The doc currently contradicts
itself about broadcast-failure retention (mentions both a removed 500-item cap
and earlier safety caps); update RUNEWAGER_FUNCTIONALITY_MAP.md to state the
single, authoritative policy: remove the 500-item cap mention and clearly
document that failures are persisted via adminLog(), surfaced through the
/broadcast_failed endpoint paginated in chunks of 30 with an overflow note, and
that a >20% failure rate triggers admin DMs — or alternatively, if you intend to
keep a cap, state the exact cap value and eviction policy instead of saying
"caps exist"; ensure the text references adminLog(), /broadcast_failed, the
30-item pagination, the 500-item cap (if retained) and the >20% threshold so
there is no ambiguity.

- 2026-02-28: PR #112 review + audit pass — fixed 10 issues: (R1) `await_tip_import_batch` dedicated pending type with JSON-array router; (R2) `generate_tooltips.sh` command-substitution pollution fixed via `RUNEWAGER_APP` env var; (R3) `add_tooltip.sh` shell-injection fixed via `TOOLTIP_TEXT_ENV`/`TOOLTIP_TMP_FILE` env vars and `<<'EOF'`; (R4) `catchAllCases` test extended with multiline patterns + `CATCH_ALL_CORES` updated; (R5) `extractCommandHandlerNames` test extended with `let`/`var`/no-semicolon fixtures; (R6) typo "auto-deletes 8s" → "auto-deletes after 8s"; (A1) dead `buildGiveawayAnnouncementText(giveaway,remainingStr)` removed; (A2) simplified `buildGiveawayAnnouncementKeyboard` with wrong callback removed; (A3+A4) duplicate `bot.action('admin_cat_system')` and `bot.action('admin_cat_support')` first registrations removed. All 60 tests pass.

---

## 26. Feature Documentation System

> **For all Claude sessions: start here, not by searching `index.js`.**

All bot functionality is documented in `docs/`:

| File | Contents |
|------|---------|
| [`docs/INDEX.md`](docs/INDEX.md) | **Primary index** — every callback, command, pending action cross-referenced to its feature doc |
| [`docs/features/01-onboarding.md`](docs/features/01-onboarding.md) | `/start`, age gate, referral, username link |
| [`docs/features/02-user-menu.md`](docs/features/02-user-menu.md) | User persistent menu, settings, submenus |
| [`docs/features/03-admin-menu.md`](docs/features/03-admin-menu.md) | Admin persistent menu, stats, tools, controls |
| [`docs/features/04-giveaway.md`](docs/features/04-giveaway.md) | Full giveaway wizard + join + finalization |
| [`docs/features/05-bonus-30sc.md`](docs/features/05-bonus-30sc.md) | 30 SC wager bonus request + admin approval |
| [`docs/features/06-promos.md`](docs/features/06-promos.md) | Promo creation, claim, admin management |
| [`docs/features/07-tooltips.md`](docs/features/07-tooltips.md) | Tooltip add/edit/view/remove/import/settings |
| [`docs/features/08-referral.md`](docs/features/08-referral.md) | Referral codes, boosts, leaderboard |
| [`docs/features/09-sshv.md`](docs/features/09-sshv.md) | Admin VPS console, security, session GC |
| [`docs/features/10-deploy-ops.md`](docs/features/10-deploy-ops.md) | Deploy, rollback, health, testall, metrics |
| [`docs/features/11-user-lookup.md`](docs/features/11-user-lookup.md) | Whois, bonus status, schema refresh |
| [`docs/features/12-group-linking.md`](docs/features/12-group-linking.md) | Group/channel link, view, remove, test |
| [`docs/features/13-bug-reports.md`](docs/features/13-bug-reports.md) | User submit, admin view/resolve/export |
| [`docs/features/14-announcements.md`](docs/features/14-announcements.md) | Broadcast builder, preview, retry |
| [`docs/features/15-misc-commands.md`](docs/features/15-misc-commands.md) | All other commands + background timers |
| [`docs/features/16-walkthrough.md`](docs/features/16-walkthrough.md) | 35-step walkthrough, nav guards, completion |
| [`docs/TODO_FUNCTIONALITY_UPGRADE.md`](docs/TODO_FUNCTIONALITY_UPGRADE.md) | T-01–T-15 upgrade log (T-01/02/03/15 resolved) |

**Mandate:** Any added/changed/removed feature → update the relevant feature `.md` + `docs/INDEX.md` + this map section, in the same commit.
9 changes: 8 additions & 1 deletion add_tooltip.sh
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,14 @@ const file = process.argv[2];
const text = process.env.TOOLTIP_TEXT_ENV;
const tmpFile = process.env.TOOLTIP_TMP_FILE;
const list = JSON.parse(fs.readFileSync(file, 'utf8'));
const maxId = list.reduce((m, t) => Math.max(m, Number(t.id) || 0), 0);
if (!Array.isArray(list)) {
throw new Error('tooltips.json must contain a JSON array');
}
// Only count entries that have a genuine finite numeric id
const numericIds = list
.map((t) => (t && Object.prototype.hasOwnProperty.call(t, 'id') ? Number(t.id) : NaN))
.filter((id) => Number.isFinite(id));
const maxId = numericIds.length ? Math.max(...numericIds) : 0;
const newId = maxId + 1;
list.push({ id: newId, text, enabled: true });
fs.writeFileSync(tmpFile, JSON.stringify(list, null, 2));
Expand Down
18 changes: 4 additions & 14 deletions deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -232,21 +232,11 @@ fi
# ---------------------------------------------------------
# 3c) Kill anything blocking the bot port before starting
# ---------------------------------------------------------
DEPLOY_PORT="$(grep -E '^PORT=' "$PROJECT_DIR/.env" 2>/dev/null | head -1 | cut -d= -f2 | tr -d '"' | tr -d "'" | tr -d $'\r')"
DEPLOY_PORT="$(grep -E '^PORT=' "$PROJECT_DIR/.env" 2>/dev/null | head -1 | cut -d= -f2 | cut -d'#' -f1 | tr -d '"' | tr -d "'" | tr -d ' ' | tr -d $'\r')"

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 PORT parsing in the new "3c) Kill anything blocking the bot port" block runs a failing grep/cut pipeline under set -euo pipefail without a trailing || true, so if .env is missing or lacks a PORT= line the non-zero exit status will abort the entire deploy script instead of falling back to the default port. [logic error]

Severity Level: Major ⚠️
- ❌ Deploy script aborts if .env missing or PORT unset.
- ⚠️ Prevents new code deployment until configuration corrected.
Suggested change
DEPLOY_PORT="$(grep -E '^PORT=' "$PROJECT_DIR/.env" 2>/dev/null | head -1 | cut -d= -f2 | cut -d'#' -f1 | tr -d '"' | tr -d "'" | tr -d ' ' | tr -d $'\r')"
DEPLOY_PORT="$(grep -E '^PORT=' "$PROJECT_DIR/.env" 2>/dev/null | head -1 | cut -d= -f2 | cut -d'#' -f1 | tr -d '"' | tr -d "'" | tr -d ' ' | tr -d $'\r' || true)"
Steps of Reproduction ✅
1. Ensure the VPS has the PR version of `deploy.sh` deployed at
`/var/www/html/Runewager/deploy.sh` (verified in `/workspace/Runewager/deploy.sh`, lines
24–38 set `set -euo pipefail` and `PROJECT_DIR`).

2. Remove or rename the `.env` file in the project directory so that `$PROJECT_DIR/.env`
does not exist, or create a `.env` file without any `PORT=` line (the deploy script does
not guard this access with `-f` at line 235, unlike BOT_TOKEN/ADMIN_IDS at lines 46–50
which explicitly check `-f "$PROJECT_DIR/.env"`).

3. Run the deploy script, e.g. `bash /var/www/html/Runewager/deploy.sh vps`; execution
proceeds through steps 0–3b successfully, then reaches the "3c) Kill anything blocking the
bot port before starting" block at lines 232–251.

4. At line 235, `DEPLOY_PORT="$(grep -E '^PORT=' "$PROJECT_DIR/.env" 2>/dev/null | head -1
| cut ... | tr -d $'\r')"` executes: since `.env` is missing (grep exit 2) or has no
`PORT=` (grep exit 1), the pipeline exits non‑zero; with `set -euo pipefail`, this
non‑zero status in a simple assignment causes the script to trigger the ERR trap and abort
the deploy before `DEPLOY_PORT` can fall back to the default
`DEPLOY_PORT="${DEPLOY_PORT:-3000}"` at line 236.
Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** deploy.sh
**Line:** 235:235
**Comment:**
	*Logic Error: The PORT parsing in the new "3c) Kill anything blocking the bot port" block runs a failing grep/cut pipeline under `set -euo pipefail` without a trailing `|| true`, so if `.env` is missing or lacks a `PORT=` line the non-zero exit status will abort the entire deploy script instead of falling back to the default port.

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.
👍 | 👎

DEPLOY_PORT="${DEPLOY_PORT:-3000}"
_BLOCKING=""
if command -v lsof >/dev/null 2>&1; then
_BLOCKING="$(lsof -ti :"$DEPLOY_PORT" 2>/dev/null || true)"
elif command -v fuser >/dev/null 2>&1; then
_BLOCKING="$(fuser -n tcp "$DEPLOY_PORT" 2>/dev/null | tr ' ' '\n' | sed '/^$/d' || true)"
fi
if [[ -n "$_BLOCKING" ]]; then
say "Port $DEPLOY_PORT blocked — killing before start…"
for _pid in $_BLOCKING; do
kill -9 "$_pid" 2>/dev/null || true
done
sleep 1
fi
# shellcheck source=scripts/helpers/free_port.sh
. "$PROJECT_DIR/scripts/helpers/free_port.sh"
free_port "$DEPLOY_PORT"

# ---------------------------------------------------------
# 4) Start bot via systemctl
Expand Down
51 changes: 35 additions & 16 deletions dev-run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,38 @@ if [ "$NODE_MAJOR" -lt 20 ] 2>/dev/null; then
exit 1
fi

# Pull latest code
echo "[dev-run] Pulling latest code from origin main..."
git -C "$ROOT_DIR" fetch origin main 2>&1 \
&& git -C "$ROOT_DIR" reset --hard origin/main 2>&1 \
|| echo "[dev-run] WARN: git pull failed — starting with local copy"
# Optionally pull latest code (off by default in dev to preserve local changes).
# Reads RUNEWAGER_AUTO_UPDATE from environment first, then from .env as fallback.
# Set RUNEWAGER_AUTO_UPDATE=1 in environment or .env to enable auto-pull.
_AUTO_UPD_DOTENV=$(grep -E '^RUNEWAGER_AUTO_UPDATE=' "$ROOT_DIR/.env" 2>/dev/null | head -1 | cut -d= -f2 | cut -d'#' -f1 | tr -d '"' | tr -d "'" | tr -d ' ' || true)
_AUTO_UPDATE="${RUNEWAGER_AUTO_UPDATE:-${_AUTO_UPD_DOTENV:-0}}"
if [ "$_AUTO_UPDATE" = "1" ]; then
echo "[dev-run] Fetching latest code from origin main..."
if git -C "$ROOT_DIR" fetch origin main 2>&1; then
# Read RUNEWAGER_FORCE_RESET from env then .env fallback (default: off).
# Only perform the destructive reset --hard when explicitly opted in.
_FORCE_RST_DOTENV=$(grep -E '^RUNEWAGER_FORCE_RESET=' "$ROOT_DIR/.env" 2>/dev/null | head -1 | cut -d= -f2 | cut -d'#' -f1 | tr -d '"' | tr -d "'" | tr -d ' ' || true)
_FORCE_RESET="${RUNEWAGER_FORCE_RESET:-${_FORCE_RST_DOTENV:-0}}"
if [ "$_FORCE_RESET" = "1" ]; then
echo "[dev-run] RUNEWAGER_FORCE_RESET=1 — running git reset --hard origin/main..."
if git -C "$ROOT_DIR" reset --hard origin/main 2>&1; then
echo "[dev-run] Code hard-reset to $(git -C "$ROOT_DIR" rev-parse --short HEAD)"
else
echo "[dev-run] WARN: git reset --hard failed — starting with local copy"
fi
else
if git -C "$ROOT_DIR" merge --ff-only origin/main 2>&1; then
echo "[dev-run] Code updated to $(git -C "$ROOT_DIR" rev-parse --short HEAD)"
else
echo "[dev-run] WARN: fast-forward merge failed (local commits diverged?). Set RUNEWAGER_FORCE_RESET=1 to hard-reset."
fi
fi
else
echo "[dev-run] WARN: git fetch failed — starting with local copy"
fi
else
echo "[dev-run] RUNEWAGER_AUTO_UPDATE not set — skipping git pull (set to 1 in .env or environment to enable)"
fi

# Refresh tooltips
TOOLTIP_SCRIPT="$ROOT_DIR/generate_tooltips.sh"
Expand All @@ -34,18 +61,10 @@ if [ -x "$TOOLTIP_SCRIPT" ]; then
fi

# Kill anything blocking port 3000 (or PORT from .env)
DEV_PORT=$(grep -E '^PORT=' "$ROOT_DIR/.env" 2>/dev/null | head -1 | cut -d= -f2 | tr -d '"' | tr -d "'" || true)
DEV_PORT=$(grep -E '^PORT=' "$ROOT_DIR/.env" 2>/dev/null | head -1 | cut -d= -f2 | cut -d'#' -f1 | tr -d '"' | tr -d "'" | tr -d ' ' || 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 dev-run PORT extraction does not strip a trailing carriage return from .env values, so on CRLF .env files the computed port (e.g. 3000\r) can cause lsof/fuser lookups to fail silently and leave the old process running on the intended port. [possible bug]

Severity Level: Major ⚠️
- ❌ dev-run.sh may not free configured port on Windows.
- ⚠️ Developers see EADDRINUSE errors starting local server.
Suggested change
DEV_PORT=$(grep -E '^PORT=' "$ROOT_DIR/.env" 2>/dev/null | head -1 | cut -d= -f2 | cut -d'#' -f1 | tr -d '"' | tr -d "'" | tr -d ' ' || true)
DEV_PORT=$(grep -E '^PORT=' "$ROOT_DIR/.env" 2>/dev/null | head -1 | cut -d= -f2 | cut -d'#' -f1 | tr -d '"' | tr -d "'" | tr -d ' ' | tr -d '\r' || true)
Steps of Reproduction ✅
1. In `/workspace/Runewager/dev-run.sh:36-50`, note that `DEV_PORT` is parsed from
`$ROOT_DIR/.env` and only quotes and spaces are stripped (`tr -d '"'`, `tr -d "'"`, `tr -d
' '`), but no carriage return is removed.

2. On a Windows or WSL development machine, create `$ROOT_DIR/.env` with CRLF endings so
the line storing the port is `PORT=3000\r\n` (for example, by editing `.env` in a Windows
editor and saving with default CRLF line endings).

3. Start any long‑running process bound to TCP port 3000 (e.g. an old `node index.js`
instance from a previous run or `python -m http.server 3000`) so that `lsof`/`fuser`
should find a listener on port `3000`.

4. Run `./dev-run.sh` from the repo root: `DEV_PORT` becomes `3000\r`, so the subsequent
`lsof -ti :"$DEV_PORT"` or `fuser -n tcp "$DEV_PORT"` at `dev-run.sh:39-42` do not match
the existing listener; `_DEV_PIDS` remains empty, no processes are killed, and when `exec
node index.js` at `dev-run.sh:54` runs, Node fails to bind with `EADDRINUSE` because the
old process is still holding port 3000.
Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** dev-run.sh
**Line:** 37:37
**Comment:**
	*Possible Bug: The dev-run PORT extraction does not strip a trailing carriage return from `.env` values, so on CRLF `.env` files the computed port (e.g. `3000\r`) can cause `lsof`/`fuser` lookups to fail silently and leave the old process running on the intended port.

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.
👍 | 👎

DEV_PORT="${DEV_PORT:-3000}"
if command -v lsof >/dev/null 2>&1; then
_DEV_PIDS=$(lsof -ti :"$DEV_PORT" 2>/dev/null || true)
elif command -v fuser >/dev/null 2>&1; then
_DEV_PIDS=$(fuser -n tcp "$DEV_PORT" 2>/dev/null | tr ' ' '\n' | sed '/^$/d' || true)
fi
if [ -n "${_DEV_PIDS:-}" ]; then
echo "[dev-run] WARN: Port $DEV_PORT blocked — killing..."
for _p in $_DEV_PIDS; do kill -9 "$_p" 2>/dev/null || true; done
sleep 1
fi
bash "$ROOT_DIR/scripts/helpers/free_port.sh" "$DEV_PORT" \
|| echo "[dev-run] WARN: free_port.sh failed (non-fatal)"

# Foreground local run (Termux-safe). Runtime env is loaded by index.js via dotenv.
echo "[dev-run] Starting Runewager in foreground (Node $(node -v))..."
Expand Down
Loading