Files
bot/scripts/recover-orphaned-processes.sh
zeekay acaeda400a Eradicate OpenClaw — resolve merge conflicts and finish the rebrand
Three coordinated things:

1. Resolve the committed upstream/main merge conflicts. 94 files were
   sitting with raw <<<<<<< HEAD ... >>>>>>> upstream/main blocks. Every
   conflict had HanzoBot/Bot on HEAD and OpenClaw on upstream. Resolved
   by keeping HEAD everywhere — 540 conflict blocks.

2. Rewrite remaining OpenClaw text refs.
     OPENCLAW   → HANZO_BOT
     OpenClaw   → HanzoBot          (PascalCase type identifiers)
     openclaw   → bot               (codebase convention — botToken,
                                    botUsername etc.; avoids invalid
                                    hyphenated JS identifiers)
     openclaw[_]/openclaw[A-Z] → bot[_]/bot[A-Z]   (compound)
     ai.openclaw.x → ai.hanzo.bot.x  (JVM package path)

3. Delete or rename openclaw-named file paths (116 of them). Dead
   duplicates with a Bot-named canonical from the partial migration:
   deleted. Otherwise renamed.
     ai/openclaw/**           → deleted (ai/hanzo/bot/** canonical)
     Sources/OpenClaw*/       → deleted (Sources/Bot* canonical)
     Tests/OpenClawIPCTests/  → deleted (Tests/BotIPCTests canonical)
     OpenClawKit/             → deleted (BotKit canonical)
     openclaw-tools.*.ts      → deleted (bot-tools.*.ts canonical)
     openclaw-root.ts etc.    → deleted (bot-root.ts canonical)
     types.openclaw.ts        → deleted (types.bot.ts canonical)
     extensions/*/openclaw.plugin.json → renamed hanzo-bot.plugin.json
     docs/start/openclaw.md   → renamed hanzo-bot.md (+ zh-CN)
     docs/assets/openclaw-*.png, whatsapp-openclaw*.jpg → deleted
     scripts/.../openclaw-*   → deleted (bot/hanzo-bot parallels)
     openclaw.mjs             → deleted (hanzo-bot.mjs is package.bin)

Then patched 65 broken JS/TS import strings where hanzo-bot had ended
up inside an import path ('./types.hanzo-bot.js' → './types.bot.js'
etc., since the on-disk filename uses the brand-neutral bot- prefix).

Pre-existing lint debt cleaned up to get oxlint --type-aware to 0/0:
removed the dead i18n test that referenced a path no longer existing;
defined the missing DIDConfig and WalletConfig types in types.base.ts
(they were imported but never declared); dropped unused imports;
collapsed redundant type assertion and a tautological meta.bot lookup
that the openclaw→bot rename made redundant.

The a2ui.bundle.js generated artifact is kept at its pre-rebrand
contents — it gets regenerated by 'pnpm canvas:a2ui:bundle' from
sources I cannot rebuild in this commit; the source side is clean.

Verified: 0 occurrences of openclaw (case-insensitive) in tree source
content, 0 file or directory paths with openclaw in the name; oxlint
--type-aware src test reports 0/0.
2026-05-11 17:56:29 -07:00

192 lines
4.8 KiB
Bash
Executable File

#!/usr/bin/env bash
# Scan for orphaned coding agent processes after a gateway restart.
#
# Background coding agents (Claude Code, Codex CLI) spawned by the gateway
# can outlive the session that started them when the gateway restarts.
# This script finds them and reports their state.
#
# Usage:
# recover-orphaned-processes.sh
#
# Output: JSON object with `orphaned` array and `ts` timestamp.
set -euo pipefail
usage() {
cat <<'USAGE'
Usage: recover-orphaned-processes.sh
Scans for likely orphaned coding agent processes and prints JSON.
USAGE
}
if [ "${1:-}" = "--help" ] || [ "${1:-}" = "-h" ]; then
usage
exit 0
fi
if [ "$#" -gt 0 ]; then
usage >&2
exit 2
fi
if ! command -v node &>/dev/null; then
_ts="unknown"
command -v date &>/dev/null && _ts="$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null)" || true
[ -z "$_ts" ] && _ts="unknown"
printf '{"error":"node not found on PATH","orphaned":[],"ts":"%s"}\n' "$_ts"
exit 0
fi
node <<'NODE'
const { execFileSync } = require("node:child_process");
const fs = require("node:fs");
let username = process.env.USER || process.env.LOGNAME || "";
if (username && !/^[a-zA-Z0-9._-]+$/.test(username)) {
username = "";
}
function runFile(file, args) {
try {
return execFileSync(file, args, {
encoding: "utf8",
stdio: ["ignore", "pipe", "ignore"],
});
} catch (err) {
if (err && typeof err.stdout === "string") {
return err.stdout;
}
if (err && err.stdout && Buffer.isBuffer(err.stdout)) {
return err.stdout.toString("utf8");
}
return "";
}
}
function resolveStarted(pid) {
const started = runFile("ps", ["-o", "lstart=", "-p", String(pid)]).trim();
return started.length > 0 ? started : "unknown";
}
function resolveCwd(pid) {
if (process.platform === "linux") {
try {
return fs.readlinkSync(`/proc/${pid}/cwd`);
} catch {
return "unknown";
}
}
const lsof = runFile("lsof", ["-a", "-d", "cwd", "-p", String(pid), "-Fn"]);
const match = lsof.match(/^n(.+)$/m);
return match ? match[1] : "unknown";
}
function sanitizeCommand(cmd) {
// Avoid leaking obvious secrets when this diagnostic output is shared.
return cmd
.replace(
/(--(?:token|api[-_]?key|password|secret|authorization)\s+)([^\s]+)/gi,
"$1<redacted>",
)
.replace(
/((?:token|api[-_]?key|password|secret|authorization)=)([^\s]+)/gi,
"$1<redacted>",
)
.replace(/(Bearer\s+)[A-Za-z0-9._~+/=-]+/g, "$1<redacted>");
}
// Pre-filter candidate PIDs using pgrep to avoid scanning all processes.
// Only falls back to a full ps scan when pgrep is genuinely unavailable
// (ENOENT), not when it simply finds no matches (exit code 1).
let pgrepUnavailable = false;
const pgrepResult = (() => {
const args =
username.length > 0
? ["-u", username, "-f", "codex|claude"]
: ["-f", "codex|claude"];
try {
return execFileSync("pgrep", args, {
encoding: "utf8",
stdio: ["ignore", "pipe", "ignore"],
});
} catch (err) {
if (err && err.code === "ENOENT") {
pgrepUnavailable = true;
return "";
}
// pgrep exit code 1 = no matches — return stdout (empty)
if (err && typeof err.stdout === "string") return err.stdout;
return "";
}
})();
const candidatePids = pgrepResult
.split("\n")
.map((s) => s.trim())
.filter((s) => s.length > 0 && /^\d+$/.test(s));
let lines;
if (candidatePids.length > 0) {
// Fetch command info only for candidate PIDs.
lines = runFile("ps", ["-o", "pid=,command=", "-p", candidatePids.join(",")]).split("\n");
} else if (pgrepUnavailable && username.length > 0) {
// pgrep not installed — fall back to user-scoped ps scan.
lines = runFile("ps", ["-U", username, "-o", "pid=,command="]).split("\n");
} else if (pgrepUnavailable) {
// pgrep not installed and no username — full scan as last resort.
lines = runFile("ps", ["-axo", "pid=,command="]).split("\n");
} else {
// pgrep ran successfully but found no matches — no orphans.
lines = [];
}
const includePattern = /codex|claude/i;
const excludePatterns = [
/hanzo-bot-gateway/i,
/signal-cli/i,
/node_modules\/\.bin\/hanzo-bot/i,
/recover-orphaned-processes\.sh/i,
];
const orphaned = [];
for (const rawLine of lines) {
const line = rawLine.trim();
if (!line) {
continue;
}
const match = line.match(/^(\d+)\s+(.+)$/);
if (!match) {
continue;
}
const pid = Number(match[1]);
const cmd = match[2];
if (!Number.isInteger(pid) || pid <= 0 || pid === process.pid) {
continue;
}
if (!includePattern.test(cmd)) {
continue;
}
if (excludePatterns.some((pattern) => pattern.test(cmd))) {
continue;
}
orphaned.push({
pid,
cmd: sanitizeCommand(cmd),
cwd: resolveCwd(pid),
started: resolveStarted(pid),
});
}
process.stdout.write(
JSON.stringify({
orphaned,
ts: new Date().toISOString(),
}) + "\n",
);
NODE