Skip to content

Phase 2: TypeScript tasks, runtime tunnel download, credential hygiene#18

Merged
jochen-testingbot merged 4 commits into
masterfrom
phase2-modernize-tasks
Jul 17, 2026
Merged

Phase 2: TypeScript tasks, runtime tunnel download, credential hygiene#18
jochen-testingbot merged 4 commits into
masterfrom
phase2-modernize-tasks

Conversation

@jochen-testingbot

@jochen-testingbot jochen-testingbot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Stacked on #17 (Phase 1). Base branch is phase1-restore-build; review/merge that first. The diff shown here is Phase 2 only.

Phase 2 of MODERNIZATION.md: modernize the two agent tasks.

What changed

TypeScript rewrite (2.1)

  • tb-main and tb-stop-tunnel are now index.ts (strict TS, typed azure-pipelines-task-lib 5 API, tl.setResult), compiled to index.js by tsc. The build gains a compile step before webpack; the copy step ignores *.ts/tests/tsconfig and ships the compiled JS.

Runtime tunnel (2.2, 2.5)

  • Deleted tb-main/tunnel/2.30.jar (Jan 2019, vulnerable Jetty) and the raw java -jar spawn. tb-main now uses testingbot-tunnel-launcher@^1.1.18, which downloads/caches the current tunnel, checks Java ≥ 11, and resolves only when the tunnel is ready — so a pre-ready exit, bad credentials or an exhausted account now fails the task. VSIX shrinks ~3.2 MB.
  • New inputs mirroring testingbot-tunnel-action: tunnelIdentifier (default azure-<BuildId>-<attempt>), sePort, proxy, readyTimeout, sharedTunnel, noBump, noCache. tbTunnelOptions kept but deprecated (warns).
  • Cross-task stop still uses the stored PID (the launcher's in-process killAsync can't reach another task's tunnel); the stop task keeps the Phase 1 guard + ESRCH handling.

Credential hygiene (2.3)

  • tl.setSecret(secret) + TB_SECRET/TESTINGBOT_SECRET exported as secret variables (masked in logs). The key stays a normal variable.

Task metadata (2.6)

  • runsOn, outputVariables, restrictions (commands: restricted + settableVariables allowlist), a tunnel input group, real instanceNameFormat, index.js target.

Tests (2.7)

  • mocha + mock-run/mock-test. tb-main no-tunnel path (registers secret, sets secret variable, writes attachment) and tb-stop-tunnel no-pid/garbage-pid guards. npm test3 passing.

/mini viewer — kept

Per maintainer feedback, the results tab keeps the embedded TestingBot /mini video/logs viewer, which the developer sees inside the build UI. Signing that share URL needs md5(key:secret:session_id), so the key/secret still travel in the build attachment and the tab is untouched here. Removing them from the browser without losing /mini means signing the URL server-side via a TestingBot API data source — that's the Phase 3 tab redesign. This PR does not regress the DX.

Verification

  • npm test → 3 passing.
  • npm run packagetestingbot.testingbot-tasks-0.2.7.vsix containing the compiled index.js for both tasks + testingbot-tunnel-launcher/azure-pipelines-task-lib in dist/tb-*/node_modules, no jar, no .ts/tests.
  • A real (non-mock) run of the compiled tb-main no-tunnel path succeeds: secret masked, attachment carries the creds /mini needs.

Residuals (later)

Summary by CodeRabbit

  • New Features

    • Added expanded tunnel configuration options, including identifiers, proxy settings, timeouts, team sharing, and caching controls.
    • Added outputs for tunnel status details and TestingBot build information.
    • Updated tunnel startup to use the current launcher and support runtime caching.
  • Bug Fixes

    • Improved validation and handling of TestingBot credentials and tunnel processes.
    • Stopping an already-ended or missing tunnel now completes gracefully.
  • Documentation

    • Updated build, packaging, architecture, and task usage guidance.

Rebuilds both agent tasks as TypeScript on azure-pipelines-task-lib 5, replaces
the bundled 2019 tunnel jar with runtime download, hardens credential handling,
and adds a mocha test suite. Stacked on the Phase 1 build restore.

Tasks (2.1)
- Rewrite tb-main and tb-stop-tunnel as index.ts (tsc -> index.js in place),
  strict TypeScript, typed task-lib 5 API, tl.setResult for pass/fail.
- Build pipeline gains a `compile` step (tsc per task) before webpack; webpack
  copy now ignores *.ts/tests/tsconfig and ships the compiled index.js.

Tunnel (2.2, 2.5)
- Replace tb-main/tunnel/2.30.jar (Jan 2019, vulnerable Jetty) + the raw
  `java -jar` spawn with testingbot-tunnel-launcher ^1.1.18: it downloads and
  caches the current tunnel, checks Java >= 11, and resolves only when the
  tunnel is ready. A pre-ready exit / bad creds / no minutes now fails the task.
- New inputs mirroring testingbot-tunnel-action: tunnelIdentifier (defaults to
  azure-<BuildId>-<attempt>), sePort, proxy, readyTimeout, sharedTunnel, noBump,
  noCache. tbTunnelOptions kept but deprecated (warns). VSIX shrinks ~3.2 MB.
- Cross-task stop still works via the stored pid (the launcher's in-process
  killAsync can't reach another task's tunnel); the stop task keeps the Phase 1
  guard + ESRCH handling.

Credentials (2.3)
- tl.setSecret(secret) + export TB_SECRET/TESTINGBOT_SECRET as secret variables
  (masked in logs). Key stays a normal variable.

Task metadata (2.6)
- runsOn Agent/DeploymentGroup, outputVariables, restrictions
  (commands: restricted + settableVariables allowlist), tunnel input group,
  real instanceNameFormat, index.js execution target.

Tests (2.7)
- mocha + azure-pipelines-task-lib mock-run/mock-test. tb-main: no-tunnel path
  registers the secret, sets it as a secret variable, writes the attachment.
  tb-stop-tunnel: no-pid and garbage-pid guards. `npm test` -> 3 passing.

/mini viewer (per maintainer): the results tab keeps the embedded TestingBot
/mini video/logs viewer, which needs the key/secret to sign the share URL. Those
credentials therefore still travel in the build attachment. Removing them from
the browser without losing /mini requires signing the URL server-side via a
TestingBot API data source -- deferred to the Phase 3 tab redesign. The tab is
untouched in this PR.

Verified: `npm test` (3 passing), `npm run package` produces
testingbot.testingbot-tasks-0.2.7.vsix containing compiled index.js + the
launcher/task-lib node_modules and no jar; a real (non-mock) run of the compiled
tb-main no-tunnel path succeeds with the secret masked and the attachment intact.

Residual (noted for later): the launcher passes key/secret as java argv
internally (finding #9, Low-Med, shared self-hosted agents); the attachment
credential exposure (finding #1) is resolved in Phase 3.
@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The pull request migrates TestingBot Azure Pipelines tasks to TypeScript entrypoints, adds compile/test packaging workflows, expands tunnel configuration, introduces runtime tunnel launching, and adds automated tests for configuration and tunnel shutdown behavior.

Changes

TestingBot task modernization

Layer / File(s) Summary
TypeScript build and packaging pipeline
package.json, scripts/*, webpack.config.js, .gitignore, CLAUDE.md
Build scripts now compile and test TypeScript task sources, install source dependencies before compilation, package compiled entrypoints, and ignore generated task artifacts.
tb-main TypeScript task and tunnel configuration
tb-main/index.ts, tb-main/task.json, tb-main/package.json, tb-main/tsconfig.json, tb-main/tests/*
The main task resolves credentials, exports variables, optionally launches the tunnel, creates the build attachment, and validates configuration through task-mock tests.
tb-stop-tunnel TypeScript shutdown task
tb-stop-tunnel/index.ts, tb-stop-tunnel/task.json, tb-stop-tunnel/package.json, tb-stop-tunnel/tsconfig.json, tb-stop-tunnel/tests/*
The shutdown task validates and kills the configured tunnel PID, reports Azure Pipelines completion status, and tests no-op, invalid-PID, exited-process, and live-process cases.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AzurePipelines
  participant TBMain
  participant TunnelLauncher
  participant BuildAttachment
  AzurePipelines->>TBMain: Provide inputs and endpoint authorization
  TBMain->>TBMain: Resolve credentials and export variables
  TBMain->>TunnelLauncher: Start tunnel with tunnel options
  TunnelLauncher-->>TBMain: Return tunnel PID and identifier
  TBMain->>BuildAttachment: Write and attach testingbot.json
  TBMain-->>AzurePipelines: Report task completion
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: TypeScript task rewrite, runtime tunnel download, and credential handling improvements.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

Brings in the CodeRabbit fixes from PR #17 (stamp-version validation,
README/CLAUDE/MODERNIZATION docs, engines.node) and applies the review points
that also affect the Phase 2 TypeScript code:

- tb-stop-tunnel is now genuinely dependency-free (honors the "stop task must
  not pull azure-pipelines-task-lib" guideline consistently): index.ts reads
  TB_TUNNEL_PID from process.env, validates the exact "pid_<positive-int>" shape
  (rejects pid_0 / negatives that would signal a process group / trailing
  garbage), handles ESRCH, and emits the raw ##vso[task.complete] command
  itself. Its package.json declares no dependencies, so nothing is installed
  into dist/tb-stop-tunnel (VSIX no longer ships task-lib twice).
- install-task-deps.js / ensure-task-src-deps.js skip tasks with no deps.
- Tests rewritten to spawn the compiled task directly (no task-lib): no-pid,
  malformed pids, ESRCH, and a real process-kill case.
- engines.node >=20 on both task package.json files.

Verified: npm test -> 8 passing; npm run package -> vsix with compiled index.js
for both tasks, tunnel-launcher under tb-main only, tb-stop-tunnel with no
node_modules, and no source .ts shipped.
@jochen-testingbot
jochen-testingbot changed the base branch from phase1-restore-build to master July 17, 2026 19:43
Restore tb-build-info/scripts/info.js to match master; a trailing-whitespace
change had slipped in during the earlier revert. The tab is Phase 3 scope.

@coderabbitai coderabbitai 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.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CLAUDE.md`:
- Around line 49-66: Update the modernization roadmap sections around the Phase
1 status to reflect that the TypeScript task entrypoints, runtime tunnel
downloads, and secret handling described by TBMain and TBStopTunnel are already
implemented. Remove or revise obsolete “pending” guidance while preserving the
roadmap’s remaining accurate phases and findings.

In `@scripts/ensure-task-src-deps.js`:
- Line 20: Update the dependency-check logic around the node_modules guard in
the task dependency setup flow so it validates installed dependencies against
the current package.json and lockfile, rather than only checking directory
existence. Reinstall or refresh dependencies when the installed metadata is
missing or stale, while preserving the skip behavior when the installed state
matches the current manifests.

In `@tb-main/index.ts`:
- Around line 89-100: Validate the parsed sePort and readyTimeout values before
assigning them to options or launching the tunnel: reject non-numeric or
partially parsed inputs, require sePort to be an integer from 1 through 65535,
and require readyTimeout to be a positive number. Preserve the existing
optional-input behavior and proxy handling in the surrounding tunnel setup flow.
- Around line 175-184: Reorder the success flow so writeAttachment(credentials)
completes before startTunnel(credentials) is called, ensuring attachment
failures occur before the tunnel is detached. Preserve the existing tunnel
condition, success result, explicit exit, and failure handling.

In `@tb-stop-tunnel/tests/_suite.ts`:
- Around line 45-61: Update the “actually kills a running process” test around
runStop and its delayed liveness check to always clean up the spawned child. Add
error handling for spawn/test failures and ensure the child is terminated in a
finally-style cleanup path, while preserving the existing assertions and done
callback behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 1408393d-b2f5-4e1c-8232-387bb07f522b

📥 Commits

Reviewing files that changed from the base of the PR and between cde4c6e and 5e25dc4.

⛔ Files ignored due to path filters (4)
  • package-lock.json is excluded by !**/package-lock.json
  • tb-main/package-lock.json is excluded by !**/package-lock.json
  • tb-main/tunnel/.DS_Store is excluded by !**/.DS_Store
  • tb-main/tunnel/2.30.jar is excluded by !**/*.jar
📒 Files selected for processing (19)
  • .gitignore
  • CLAUDE.md
  • package.json
  • scripts/ensure-task-src-deps.js
  • scripts/install-task-deps.js
  • tb-main/index.ts
  • tb-main/package.json
  • tb-main/task.json
  • tb-main/testingbot.js
  • tb-main/tests/_suite.ts
  • tb-main/tests/no-tunnel.ts
  • tb-main/tsconfig.json
  • tb-stop-tunnel/index.ts
  • tb-stop-tunnel/package.json
  • tb-stop-tunnel/sample.js
  • tb-stop-tunnel/task.json
  • tb-stop-tunnel/tests/_suite.ts
  • tb-stop-tunnel/tsconfig.json
  • webpack.config.js
💤 Files with no reviewable changes (2)
  • tb-stop-tunnel/sample.js
  • tb-main/testingbot.js

Comment thread CLAUDE.md
Comment on lines +49 to +66
- **`tb-main/`** — Pipeline task "TestingBot Configuration" (`TBMain`). TypeScript
task (`index.ts`) that reads a TestingBot service-endpoint credential, exports
`TB_KEY`/`TB_SECRET`/`SELENIUM_HOST`/etc. as pipeline variables (the secret ones
via `setSecret` + a secret variable) so downstream test steps can reach the
grid, and optionally launches **TestingBot Tunnel** via the
`testingbot-tunnel-launcher` npm package (which downloads/caches the current
tunnel jar at runtime — nothing is bundled). It writes `testingbot.json` and
attaches it to the build via `task.addattachment` with type
`TestingBotBuildResult` — this attachment is how the results tab (below) later
finds the build's credentials and build name. The credentials still travel in
that attachment because the tab needs them to sign TestingBot `/mini` share
URLs; removing them from the browser (finding #1) is the Phase 3 tab redesign.

- **`tb-stop-tunnel/`** — Pipeline task "Stop TestingBot Tunnel" (`TBStopTunnel`).
Kills the tunnel process using the PID that `tb-main` stored in the
`TB_TUNNEL_PID` variable. Add this task after your test steps to tear the tunnel
down.
TypeScript task (`index.ts`) that kills the tunnel process using the PID that
`tb-main` stored in the `TB_TUNNEL_PID` variable (the launcher's in-process
`killAsync` can't reach a tunnel started by a different task). Add this task
after your test steps to tear the tunnel down.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update the stale modernization roadmap.

These sections describe TypeScript task entrypoints, runtime tunnel downloads, and secret handling as implemented, but lines 121-123 still say this is Phase 1 and those capabilities are pending. Update the roadmap so contributors do not follow obsolete project guidance.

Also applies to: 85-98

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CLAUDE.md` around lines 49 - 66, Update the modernization roadmap sections
around the Phase 1 status to reflect that the TypeScript task entrypoints,
runtime tunnel downloads, and secret handling described by TBMain and
TBStopTunnel are already implemented. Remove or revise obsolete “pending”
guidance while preserving the roadmap’s remaining accurate phases and findings.

const taskDir = path.join(root, entry);
const pkgPath = path.join(taskDir, 'package.json');
if (!fs.existsSync(pkgPath)) continue;
if (fs.existsSync(path.join(taskDir, 'node_modules'))) continue;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Validate the installed dependency state, not only the directory’s existence.

Line 20 skips installation whenever node_modules exists, including after an interrupted install or when package.json/the lockfile has changed. That can leave tsc resolving stale or missing declarations and make npm run compile fail based on workspace history. Track the installed manifest/lockfile or refresh dependencies when metadata is out of date.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/ensure-task-src-deps.js` at line 20, Update the dependency-check
logic around the node_modules guard in the task dependency setup flow so it
validates installed dependencies against the current package.json and lockfile,
rather than only checking directory existence. Reinstall or refresh dependencies
when the installed metadata is missing or stale, while preserving the skip
behavior when the installed state matches the current manifests.

Comment thread tb-main/index.ts
Comment on lines +89 to +100
const sePort = tl.getInput('sePort');
if (sePort) {
options['se-port'] = parseInt(sePort, 10);
}
const proxy = tl.getInput('proxy');
if (proxy) {
options.proxy = proxy;
}
const readyTimeout = tl.getInput('readyTimeout');
if (readyTimeout) {
options.timeout = parseInt(readyTimeout, 10);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Validate numeric tunnel inputs before launching.

parseInt accepts values such as 12abc and produces NaN for non-numeric input. Reject invalid ports/timeouts explicitly, including ports outside 1–65535 and non-positive timeouts.

Proposed fix
   const sePort = tl.getInput('sePort');
   if (sePort) {
-    options['se-port'] = parseInt(sePort, 10);
+    const port = Number(sePort);
+    if (!Number.isInteger(port) || port < 1 || port > 65535) {
+      throw new Error('Selenium relay port must be an integer from 1 to 65535.');
+    }
+    options['se-port'] = port;
   }
@@
   const readyTimeout = tl.getInput('readyTimeout');
   if (readyTimeout) {
-    options.timeout = parseInt(readyTimeout, 10);
+    const timeout = Number(readyTimeout);
+    if (!Number.isInteger(timeout) || timeout < 1) {
+      throw new Error('Ready timeout must be a positive integer.');
+    }
+    options.timeout = timeout;
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const sePort = tl.getInput('sePort');
if (sePort) {
options['se-port'] = parseInt(sePort, 10);
}
const proxy = tl.getInput('proxy');
if (proxy) {
options.proxy = proxy;
}
const readyTimeout = tl.getInput('readyTimeout');
if (readyTimeout) {
options.timeout = parseInt(readyTimeout, 10);
}
const sePort = tl.getInput('sePort');
if (sePort) {
const port = Number(sePort);
if (!Number.isInteger(port) || port < 1 || port > 65535) {
throw new Error('Selenium relay port must be an integer from 1 to 65535.');
}
options['se-port'] = port;
}
const proxy = tl.getInput('proxy');
if (proxy) {
options.proxy = proxy;
}
const readyTimeout = tl.getInput('readyTimeout');
if (readyTimeout) {
const timeout = Number(readyTimeout);
if (!Number.isInteger(timeout) || timeout < 1) {
throw new Error('Ready timeout must be a positive integer.');
}
options.timeout = timeout;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tb-main/index.ts` around lines 89 - 100, Validate the parsed sePort and
readyTimeout values before assigning them to options or launching the tunnel:
reject non-numeric or partially parsed inputs, require sePort to be an integer
from 1 through 65535, and require readyTimeout to be a positive number. Preserve
the existing optional-input behavior and proxy handling in the surrounding
tunnel setup flow.

Comment thread tb-main/index.ts
Comment on lines +175 to +184
if (tl.getBoolInput('tbTunnel', false)) {
await startTunnel(credentials);
}

writeAttachment(credentials);
tl.setResult(tl.TaskResult.Succeeded, 'TestingBot configured.');
// Exit explicitly: a running tunnel child keeps the event loop alive.
process.exit(0);
} catch (err) {
tl.setResult(tl.TaskResult.Failed, err instanceof Error ? err.message : String(err));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Prevent a detached tunnel from surviving task failure.

startTunnel detaches the child before writeAttachment can throw. That failure marks the task failed but leaves the tunnel running, and a normally conditioned stop task may then be skipped. Write the attachment before starting the tunnel or retain and terminate the child in the catch path.

Minimal fix
     const credentials = getEndpointDetails('connectedServiceName');
     exportVariables(credentials);
+    writeAttachment(credentials);

     if (tl.getBoolInput('tbTunnel', false)) {
       await startTunnel(credentials);
     }

-    writeAttachment(credentials);
     tl.setResult(tl.TaskResult.Succeeded, 'TestingBot configured.');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (tl.getBoolInput('tbTunnel', false)) {
await startTunnel(credentials);
}
writeAttachment(credentials);
tl.setResult(tl.TaskResult.Succeeded, 'TestingBot configured.');
// Exit explicitly: a running tunnel child keeps the event loop alive.
process.exit(0);
} catch (err) {
tl.setResult(tl.TaskResult.Failed, err instanceof Error ? err.message : String(err));
const credentials = getEndpointDetails('connectedServiceName');
exportVariables(credentials);
writeAttachment(credentials);
if (tl.getBoolInput('tbTunnel', false)) {
await startTunnel(credentials);
}
tl.setResult(tl.TaskResult.Succeeded, 'TestingBot configured.');
// Exit explicitly: a running tunnel child keeps the event loop alive.
process.exit(0);
} catch (err) {
tl.setResult(tl.TaskResult.Failed, err instanceof Error ? err.message : String(err));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tb-main/index.ts` around lines 175 - 184, Reorder the success flow so
writeAttachment(credentials) completes before startTunnel(credentials) is
called, ensuring attachment failures occur before the tunnel is detached.
Preserve the existing tunnel condition, success result, explicit exit, and
failure handling.

Comment on lines +45 to +61
it('actually kills a running process', function (done) {
const child = spawn(process.execPath, ['-e', 'setInterval(() => {}, 1000)'], { stdio: 'ignore' });
child.on('spawn', () => {
const { status } = runStop(`pid_${child.pid}`);
assert.strictEqual(status, 0);
// Give the signal a moment, then confirm the process is gone.
setTimeout(() => {
let alive = true;
try {
process.kill(child.pid as number, 0);
} catch (err) {
alive = (err as NodeJS.ErrnoException).code !== 'ESRCH';
}
assert.strictEqual(alive, false, 'child process should have been killed');
done();
}, 200);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Always clean up the spawned test process.

If runStop, an assertion, or the delayed liveness check fails, the interval child can remain alive and keep CI running. Add an error handler and terminate the child from a finally/cleanup path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tb-stop-tunnel/tests/_suite.ts` around lines 45 - 61, Update the “actually
kills a running process” test around runStop and its delayed liveness check to
always clean up the spawned child. Add error handling for spawn/test failures
and ensure the child is terminated in a finally-style cleanup path, while
preserving the existing assertions and done callback behavior.

@jochen-testingbot
jochen-testingbot merged commit 6397f7f into master Jul 17, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant