CLICLItypeA command you run, rather than a dependency you import.See every one →What this type is →independently maintainedindependently maintainedoriginPublished by one person or a small company outside SAP. Says nothing about quality; the readings say that.See every one →

@saptools/cf-debugger

Opens a verified Node.js inspector tunnel over cf ssh to an app on SAP BTP Cloud Foundry from one command, with its own local port for each concurrent session.

Attaching an IDE debugger to a Node.js app on Cloud Foundry means finding the right process in the container, starting its inspector and forwarding a port by hand. cf-debugger start does those steps in one command: it picks the Node PID, sends SIGUSR1, spawns cf ssh -N -L and reports ready only once GET /json/list through the tunnel returns an attachable target. Each session gets a local port in the 20000–20999 range and an isolated CF_HOME, so several apps can be debugged at once, and doctor reports what an interrupted session left behind. It opens the tunnel and stops there: breakpoints are set in whatever attaches to the local port, and an app with SSH disabled is restarted only when --allow-ssh-enable-restart is passed.

Facts

Latestv0.3.0ReleasedLast pushLicenceMIT

Where it sits

Lives in
Provides
DebuggingCAPDebuggingcapabilitySeeing what running code did between an input arriving and a result coming out: which code ran, what the values were at each point, and why a value ended up as it did.
Runs
Cloud Foundry
Requires
Node.jsNode.jsoutside the atlasRuns on Node.js 20 or newer, beside the official cf CLI that it drives.SAP BTP, Cloud Foundry RuntimeSAP BTP, Cloud Foundry RuntimeartifactThe target is a Node.js app running on SAP BTP Cloud Foundry, reached through cf ssh.

Who is behind it

Đông TrầnĐông TrầnPersonMaintains this
In the atlas: maintains 18, contributes to 18
@dongitran
maintainer

What it declares

Every artifact

Type
CLI
Origin
Independently maintained
Runs on
Cloud Foundry

As a CLI

Distributed through
npm

Connections4

Install

npm add @saptools/cf-debugger

Derived from the package name. No recipe recorded; the README has the configuration.

Where @saptools/cf-debugger runsYour SAP BTP subaccountBy its type: a CLI is run from a terminalcallsdocumentedrequiresdocumentedrequiresYour terminal, actorYour terminalNode.js, systemNode.jssystem@saptools/cf-debugger, module@saptools/cf-debuggerSAP BTP, Cloud Foundry Runtime, service instanceSAP BTP, CloudFoundry Runtime
SAPnot SAP's, or the project's ownstated in the documentdocumented only

README

quoted from dongitran/saptools under MIT, read
dongitran/saptools/packages/cf-debugger/README.mdOn GitHub

@saptools/cf-debugger

Open a verified Node.js inspector tunnel to an SAP BTP Cloud Foundry app from one command. Each concurrent session has its own local port, isolated CF_HOME, and lock-guarded state entry.

Install

npm install -g @saptools/cf-debugger

Node.js 20+ and the official cf CLI are required. CF CLI v8 is recommended. Non-web process targeting uses cf ssh --process and requires CF CLI v7+. On macOS, lsof is a soft dependency used to prove which PID owns a local listening port. Install it if cf-debugger reports TUNNEL_OWNER_UNVERIFIED. Without it, an explicit stop refuses to signal a state-recorded PID whose port ownership cannot be proved; teardown still completes when the spawned child is independently confirmed dead.

Updates

Every command first checks npm for a newer @saptools/cf-debugger (at most once an hour, one small request with a 2-second timeout) and, when one exists, installs that exact version with the package manager that owns the running binary and re-runs the command you typed on the new version. Both steps are announced on stderr; nothing is printed when the install is already current:

cf-debugger: updating 0.3.0 -> 0.4.0 ...
cf-debugger: updated to 0.4.0; re-running the command

If the install cannot complete, one stderr line gives the manual command and the command runs on the installed version; that version is not retried for a day. cf-debugger self-update forces the check and install now; cf-debugger self-update --check only reports.

ControlEffect
SAPTOOLS_AUTO_UPDATE=on|notify|offon (default) installs and re-runs; notify prints the manual command once per version; off never checks. Applies to every @saptools CLI.
CF_DEBUGGER_AUTO_UPDATEsame values, this CLI only; wins over the global variable
SAPTOOLS_UPDATE_INTERVAL_MINUTESminutes between checks (default 60; 0 checks on every run)
SAPTOOLS_NPM_REGISTRYregistry to check and install from (default: npm's configured registry, then npmjs)
SAPTOOLS_UPDATE_DEBUG=1explain on stderr why nothing happened

The updater switches itself off in CI (CI set), under NODE_ENV=test or NO_UPDATE_NOTIFIER, when the binary runs from a source checkout, an npm link or an npx cache, and inside the re-run itself. It never writes to stdout, never asks for input, never uses sudo, and never moves onto a prerelease. Its state lives in ~/.saptools/updates/.

Quick start

export SAP_EMAIL="you@company.com"
export SAP_PASSWORD="your-sap-password"

cf-debugger start \
  --region eu10 \
  --org my-org \
  --space dev \
  --app my-app \
  --verbose

Successful output includes the complete attachment target:

Debugger ready for my-app (eu10/my-org/dev).
  Process:     web
  Instance:    0
  Local port:  20142
  Remote port: 9229
  Session id:  550e8400-e29b-41d4-a716-446655440000
  Tunnel PID:  83421
  Node PID:    4312
Press Ctrl+C to stop.

Attach the IDE to 127.0.0.1:20142. Node derives the advertised webSocketDebuggerUrl from the incoming request's Host header, so a URL returned through the forward already names the local forwarded port and is directly usable. cf-debugger validates that the URL is well formed but does not print or follow it.

CLI

start
cf-debugger start --region eu10 --org my-org --space dev --app my-app
cf-debugger start eu10/my-org/dev/my-app --remote-port 9230
cf-debugger start my-app --process worker --instance 2 --node-pid 4312
cf-debugger start my-app --startup-timeout 300 --timeout 180 --verbose

When region, org, or space is omitted, the current cf target supplies it.

FlagMeaning
--region <key>SAP region key; defaults to the current CF target
--api-endpoint <url>Absolute HTTPS endpoint override, including private/nonstandard hosts
--org <name>, --space <name>, --app <name>CF target and app
--process <name>CF process, default web
-i, --instance <index>Zero-based process instance, default 0
--node-pid <pid>Exact remote Node PID
--port <number>Preferred local port; otherwise choose from 20000–20999
--remote-port <number>Port where cf-debugger looks for an existing remote inspector, default 9229
--timeout <seconds>Local-tunnel and inspector-readiness budget, default 180
--startup-timeout <seconds>Overall startup deadline, default 300, maximum 1800
--allow-ssh-enable-restartPermit app-level SSH enablement and one app restart
--no-ssh-enable-restartExplicitly forbid that mutation, overriding flag/environment
--verbosePrint status, retry, and redacted tunnel transport diagnostics

Important

App restart is opt-in in 0.2.0. By default, a disabled-SSH app fails with SSH_NOT_ENABLED and no deployment mutation. Use --allow-ssh-enable-restart, or set CF_DEBUGGER_ALLOW_RESTART=1, only when restarting the named app is acceptable. CF_DEBUGGER_ALLOW_RESTART=0 keeps the guard enabled for a production shell and also vetoes a programmatic allowSshEnableRestart: true. The environment value 1 can opt in the CLI, but never grants permission to a programmatic caller by itself.

--api-endpoint accepts arbitrary hostnames but requires an absolute https: URL with no userinfo, query, fragment, or non-root path. Invalid values are rejected before cf api or cf auth. Version 0.2.2 intentionally rejects plaintext http:// landscapes because credentials would be sent to that endpoint.

Even with permission, a restart occurs only when all of these are true:

  1. the one-shot SSH error specifically says SSH support is disabled;
  2. cf ssh-enabled <app> proves app-level SSH is disabled;
  3. this invocation runs cf enable-ssh <app>;
  4. a second probe proves app-level SSH is now enabled.

An unknown probe result, an already-enabled app, a permission error, or a space-level SSH policy never causes a restart. Before an allowed restart, stderr names the app and org/space. With an explicit --node-pid, automatic restart is always rejected because the container replacement invalidates that PID.

Read-only CF commands have a 60-second per-attempt cap and retry transient transport errors with bounded exponential delays while the single overall startup budget still has time. Deployment-mutating commands have a separate 180-second cap and are never retried: a timed-out enable/restart reports CF_MUTATION_TIMEOUT because the server-side mutation may still be completing. cf auth has no outer retry loop, and rejected credentials fail after one attempt; repeated manual failures may count toward a tenant's identity-provider lockout policy. The one-shot SSH signal and readiness wait are also clamped to the same overall deadline. No retry configuration multiplies startup work past --startup-timeout. After a timeout, fail-closed tunnel and state cleanup still runs; that safety teardown can briefly extend the observed command wall time rather than abandoning an unverified child.

stop
cf-debugger stop --session-id 550e8400-e29b-41d4-a716-446655440000
cf-debugger stop eu10/my-org/dev/my-app
cf-debugger stop my-app --api-endpoint https://api.cf.eu10.hana.ondemand.com
cf-debugger stop my-app --node-pid 4312
cf-debugger stop --all
cf-debugger stop --session-id 550e8400-e29b-41d4-a716-446655440000 --force

stop --all attempts every local session, reports stopped, forced, stale, pending, and failed outcomes separately, and exits 70 if any tunnel cleanup failed (1 for other failures). It cannot be combined with a positional selector, --session-id, or target selector options; ambiguous scope is rejected instead of widening to every session.

stop --force means “forget safely,” not “kill harder.” It never signals a PID whose tunnel ownership is unproven. It forgets the state record first, then best-effort removes only the exact derived v2 CF_HOME. A deletion failure is non-fatal and warns that the retained path may contain a live refresh token; doctor then reports it as an orphan eligible for doctor --cleanup. The warning also names the abandoned PID and port for manual investigation. If a damaged record names a non-owned home path, that path is left untouched while the record is still forgotten.

list and status
cf-debugger list
cf-debugger status --session-id 550e8400-e29b-41d4-a716-446655440000
cf-debugger status eu10/my-org/dev/my-app --api-endpoint https://api.example

list prunes only records proven stale. PID liveness is paired with an optional OS process-start token to detect PID reuse; older records without the additive token retain the compatible PID-only check. A ready record is healthy only when the complete listener-owner set contains its recorded tunnel PID. Ownership that cannot be inspected remains explicit rather than being guessed.

status prints null only when no exact session matches. Ambiguity is a coded SESSION_AMBIGUOUS failure; refine with session ID, API endpoint, or Node PID.

doctor
cf-debugger doctor
cf-debugger doctor --cleanup

The default is read-only JSON reporting. It includes:

  • every local state record with its health verdict and reason;
  • v2 session homes with no state record;
  • listeners in 20000–20999 that no state record claims;
  • leftover state temp, lock, recovery, stop-intent, and corrupt-backup files;
  • legacy v1 state/homes, home count, parseable claimed sessions, conservative PID liveness, and their credential-retention risk.

--cleanup removes only canonical orphan v2 homes and sufficiently old, package-owned temp/lock/recovery/stop-intent artifacts. It revalidates orphan homes against lock-guarded state immediately before deletion. It never signals an unclaimed listener, removes corrupt evidence, or removes legacy v1 artifacts. Corrupt backups include a manual-removal command in the report while remaining ineligible for automatic cleanup. A symlinked v2 homes root is reported and never traversed.

Region resolution

Curated region keys are available from listKnownRegionKeys(). Keys matching aa00 or aa00-000 also work before the curated table is updated: cf-debugger synthesizes https://api.cf.<key>.hana.ondemand.com, or the .platform.sapcloud.cn domain for cn*, and warns with the synthesized URL. This supports new regions but makes a syntactically valid typo such as eu99 reach DNS; verify the warning or use --api-endpoint. Malformed keys fail early with UNKNOWN_REGION.

How it works

  1. Resolve and validate the endpoint, deadline, target, and credentials.
  2. Prune only provably stale state and register a unique session/port.
  3. Run cf api, environment-only cf auth, cf target, and an app existence check.
  4. Probe the selected instance through one-shot cf ssh.
  5. If an inspector already owns the remote port and is Node, reuse that PID.
  6. Otherwise:
    • choose the sole Node PID when exactly one exists;
    • with several Node PIDs, use the app $PORT listener only as a tiebreaker;
    • otherwise fail NODE_PROCESS_AMBIGUOUS.
  7. Send SIGUSR1 when needed and prove the selected PID owns the configured remote inspector port.
  8. Spawn detached cf ssh -N -L <local>:localhost:<remote>.
  9. Prove the spawned PID is among all owners of the local listener.
  10. Request GET /json/list through the tunnel and require HTTP 200 plus a non-empty target array whose first entry has a well-formed webSocketDebuggerUrl.
  11. Persist ready state and return a DebuggerHandle.

Node's Linux cluster default can leave the app $PORT socket owned by the cluster primary rather than a request-handling worker. When several Node processes exist, inspect the reported candidates and pass --node-pid explicitly if a worker is the intended target.

The remote probe reads /proc/<pid>/exe, not argv. Its marker lines are filtered by a strict saptools-inspector-* prefix, so CF SSH banners cannot impersonate results. GNU find -lname is capability-probed and is normally fast; the portable fallback walks descriptors with readlink and can take roughly 10–12 seconds in a descriptor-heavy non-GNU container.

--remote-port does not choose the port opened by SIGUSR1; Node always uses its default inspector port for that signal. A non-default value is therefore for an app already started with matching --inspect=<port>.

Status sequence

The public onStatus sequence is:

starting → logging-in → targeting → signaling
         → [ssh-enabling → ssh-restarting → signaling]
         → tunneling → ready → stopping → stopped

Verbose retries repeat the current phase with a message and remaining budget. After starting is emitted, startup failure emits terminal error and never follows it with stopped. Option/credential/region validation can reject before the status stream begins, in which case no callback is emitted. Unexpected tunnel failure after readiness likewise emits terminal error; cleanup still runs without claiming a normal stop sequence.

State and security

~/.saptools/cf-debugger-state-v2.json
~/.saptools/cf-debugger-state-v2.lock
~/.saptools/cf-debugger-homes-v2/<sessionId>/

State is written as a 0600 temp file plus atomic rename while a token-owned open(..., "wx") lock is held. The parent and session-home directories are 0700. Keep ~/.saptools on a local filesystem: exclusive-create locking is not reliably atomic on every NFS/SMB implementation. A generous age fallback recovers foreign-host lock files, but it cannot make a network filesystem's locking semantics safe.

Each isolated CF_HOME contains CF CLI credentials, including a live refresh token. A clean stop removes it. If TUNNEL_TERMINATION_FAILED retains state and the home, recover the process first, then run stop --force. If a record has already disappeared but its canonical home remains, use doctor --cleanup. Do not leave recovered homes indefinitely.

A damaged entry no longer erases healthy sessions. Invalid entries are dropped individually, and the original state file is moved to a private .corrupt-<timestamp>-<uuid> backup before any repair. Completely invalid JSON, version, or root shape is likewise preserved before a fresh empty v2 file is written.

During mixed-version use, an older cf-debugger sharing this v2 file may strip optional fields it does not understand, including process-identity and startup budget data. Newer code treats an absent/legacy identity as “cannot tell” and retains the record with PID-only compatibility rather than pruning it; doctor surfaces that degraded verdict. Updating every installed consumer is the only way to restore the stronger identity check persistently.

Legacy ~/.saptools/cf-debugger-homes/ directories may still contain live refresh and access tokens. doctor reports but never deletes them. After confirming no v1 tunnel is running, remove them explicitly:

rm -rf "$HOME/.saptools/cf-debugger-homes" \
       "$HOME/.saptools/cf-debugger-state.json" \
       "$HOME/.saptools/cf-debugger-state.lock"

The v2 CLI never adopts or mutates v1 sessions. Prefer CLI/API operations over editing the v2 JSON by hand.

Credentials are passed to cf auth as CF_USERNAME and CF_PASSWORD in the child environment, never argv. CF command and tunnel diagnostics are bounded and redact sensitive values before they reach errors or verbose output. CF_COLOR=false is forced so ANSI escapes cannot corrupt output parsing.

Environment variables

VariableMeaning
SAP_EMAIL, SAP_PASSWORDCF authentication credentials
CF_DEBUGGER_ALLOW_RESTART=0|1CLI default; 0 is also a hard veto for the programmatic API
CF_DEBUGGER_CF_BINReplace the cf executable; useful for wrappers and deterministic test stubs

A caller-provided child environment cannot override the per-session CF_HOME.

Errors and exit status

Documented domain failures use Error [CODE]: .... An unexpected operating system or filesystem failure can still surface as a plain Error without a domain code; it is never converted into success.

CodeMeaning
UNKNOWN_REGION, UNSAFE_INPUT, MISSING_CREDENTIALSInvalid endpoint/key, selector, numeric input, or credentials
CF_LOGIN_FAILED, CF_AUTH_FAILED, CF_TARGET_FAILED, CF_CLI_FAILED, CF_CLI_TIMEOUTCF command failure; rejected auth is not retried
CF_MUTATION_TIMEOUTA deployment mutation timed out, was not retried, and may still be completing remotely
STARTUP_TIMEOUT, ABORTEDOverall deadline expired, or caller/stop intent cancelled startup
APP_NOT_FOUNDThe targeted app does not exist in the selected org/space
SSH_NOT_ENABLED, SSH_PERMISSION_DENIED, SSH_STATE_UNKNOWNRestart refused/not useful, permission denied, or SSH state ambiguous
NODE_PID_RESTART_UNSAFEExplicit PID cannot survive an app restart
NODE_PROCESS_NOT_FOUND, NODE_PROCESS_AMBIGUOUS, NODE_PID_INVALIDRemote Node selection failed closed
USR1_SIGNAL_FAILED, INSPECTOR_NOT_READY, INSPECTOR_OWNER_MISMATCH, INSPECTOR_OUTPUT_TOO_LARGERemote signal/ownership protocol failed
TUNNEL_NOT_READYThe spawned local forward did not bind
PORT_UNAVAILABLE, TUNNEL_PROCESS_MISSINGLocal port allocation failed, or the spawned tunnel exposed no PID
TUNNEL_OWNER_UNVERIFIED, TUNNEL_OWNER_MISMATCHLocal listener ownership could not be proved
INSPECTOR_UNREACHABLELocal forward bound, but /json/list did not prove an attachable remote inspector
TUNNEL_EXITED, TUNNEL_TERMINATION_FAILED, TUNNEL_OWNERSHIP_UNVERIFIEDTunnel died, cleanup could not terminate it, or stop could not safely signal it
SESSION_ALREADY_RUNNING, SESSION_AMBIGUOUS, SESSION_NOT_FOUNDSession selection conflict
SESSION_STATE_LOST, SESSION_STATE_CONFLICT, STATE_LOCK_TIMEOUTAtomic state/lifecycle invariant failed
CF_HOME_CLEANUP_FAILEDSession state was removed but lifecycle cleanup could not remove its exact credential-bearing CF home
PACKAGE_METADATA_INVALIDRuntime package metadata has no usable version
STOP_FAILEDOne entry in a batch stop failed with a non-coded internal error
ExitMeaning
0Clean completion
1Coded operational/startup error, including STARTUP_TIMEOUT
70Tunnel or lifecycle cleanup failed; ownership state or a credential-bearing home may remain
SSH child's nonzero codeAn unexpected numeric SSH exit is preserved, for example 255
128 + signalTunnel child died from a signal, for example 137 for SIGKILL
130, 143User interrupted with SIGINT or SIGTERM

Programmatic API

import {
  startDebugger,
  stopDebugger,
  stopAllDebuggers,
  listSessions,
  runDoctor,
} from "@saptools/cf-debugger";

const handle = await startDebugger({
  region: "eu10",
  org: "my-org",
  space: "dev",
  app: "my-app",
  startupTimeoutMs: 300_000,
  allowSshEnableRestart: false,
});

// Attach to handle.session.localPort.
await handle.dispose();

const summary = await stopAllDebuggers();
// Outcomes and counts distinguish stopped/forced/stale/pending/failed.

Persisted schema additions in 0.2.0 are optional. Absence means compatible fallback behavior because an older mixed-version writer may strip fields it does not know. State remains version "2".

FAQ

Does this modify the remote app?

Not by default. Inspector activation sends SIGUSR1 to the verified Node process, but app-level SSH enablement and cf restart require explicit --allow-ssh-enable-restart permission. Even with permission, restart occurs only after this run changes app SSH from proven disabled to proven enabled; an already-enabled, unknown, permission-denied, or space-policy result never restarts the app.

Is session state always removed when the CLI exits?

Only after cleanup is verified. TUNNEL_TERMINATION_FAILED deliberately retains state and the isolated credential-bearing CF_HOME; recover it with doctor and stop --force instead of assuming the next start can proceed.

Development

pnpm --filter @saptools/cf-debugger lint
pnpm --filter @saptools/cf-debugger typecheck
pnpm --filter @saptools/cf-debugger test:unit
pnpm --filter @saptools/cf-debugger build

The fake-backed tests set CF_DEBUGGER_CF_BIN. Live E2E tests require a safe non-production CF target and credentials; do not run them against production.

MIT, read

History

Latest

v0.3.0

Other 1

Notes
  • Self-updating. Every command now checks npm for a newer @saptools/cf-debugger (at most once an hour) and, when one exists, installs that exact version and re-runs the command on it, announcing both steps on stderr. SAPTOOLS_AUTO_UPDATE=on|notify|off controls it (see the README's Updates section); it is off by itself in CI, in tests, from a source checkout and inside the re-run. New cf-debugger self-update [--check] forces the check and install now. --version now reads package.json at runtime. Both come from the private, build-time-bundled @saptools/core package; the published tarball gains no runtime dependency.
  1. v0.2.2

    Fixes, Other

    Notes

    This security and correctness patch hardens endpoint validation, deployment mutation retries, and forced-stop recovery while keeping state on additive schema v2. Security behavior change: --api-endpoint and the corresponding programmatic start option now reject plaintext or malformed endpoints that older releases accepted, because authentication credentials would otherwise be sent to that endpoint.

    P0 — mutation, recovery, and credential safety
    1. P0-1: cf enable-ssh and cf restart are explicitly classified as non-retryable mutations. A timeout issues no second mutation and reports CF_MUTATION_TIMEOUT, warning that the platform operation may still be in flight; read-only transient failures retain bounded retries.
    2. P0-2: stop --force now removes the state record before best-effort deletion of its exact owned CF_HOME. An undeletable home produces a token-risk warning and becomes visible to doctor instead of trapping the session in state forever.
    3. P0-3 (breaking): API endpoint overrides must be absolute HTTPS URLs with no userinfo, query, fragment, or non-root path. Invalid CLI and library values fail as UNSAFE_INPUT before any cf invocation or authentication.
    P1 — ownership and cross-version resilience
    1. P1-4: timed-out one-shot SSH commands can terminate a still-open child spawned by this process even when the OS cannot provide a birth token; forced escalation remains identity-gated.
    2. P1-5: macOS process identities now force TZ=UTC and LC_ALL=C and use versioned tokens. Legacy, malformed, or future token formats degrade to an unverified/retain verdict instead of pruning a live session.
    3. P1-6: expensive listener ownership probes run outside the global state lock on the normal path. A changed or stale candidate is revalidated under the persistence lock before any prune, closing the TOCTOU gap.
    4. P1-7: doctor --cleanup refuses to traverse a symlinked or non-directory v2 homes root and reports non-directory entries; a symlinked parent ~/.saptools remains supported.
    5. P1-8: startup cancellation keeps the lock-free sidecar fast path and again treats state stopRequestedAt or a missing record as compatibility stop signals, at the existing 500 ms cadence.
    6. P1-9: doctor now identifies PID-only fallback when a mixed-version writer stripped the optional process-identity token.
    P2 — platform and outcome correctness
    1. P2-1: loopback inspector HTTP checks opt out of the process-wide HTTP agent, so environment proxy support cannot route /json/list away from 127.0.0.1.
    2. P2-2: inspector attempt timeouts grow from 2.5 to 5 to 10 seconds while preserving retry and overall-budget headroom, allowing slow healthy inspectors to prove readiness.
    3. P2-3: tunnel readiness budgets local bind, both ownership checks, and the inspector phase inside one deadline. A slow ownership command can no longer consume the inspector's reserved HTTP window.
    4. P2-4: socket error listeners are installed before timeout/connect and all derived TCP timeouts are clamped positive, preventing a negative-timeout process crash.
    5. P2-5: lifecycle cleanup no longer treats missing lsof as proof that a confirmed-dead owned child survived; it reports the lost diagnostic without silently assuming termination.
    6. P2-6: ready-session stop intents are cleared after use and the tunnel close handler cross-checks state, preventing stale sidecars from turning a later crash into exit 0.
    7. P2-7: stop --all distinguishes forced outcomes and returns cleanup exit code 70 when any captured outcome contains TUNNEL_TERMINATION_FAILED.
    8. P2-8: CF_DEBUGGER_ALLOW_RESTART=0 is now a hard veto for library calls as well as the CLI; environment value 1 never grants a programmatic caller permission by itself.
    9. P2-9: duplicate session IDs keep the first valid record and drop only later copies, preserving ownership evidence for a possibly live tunnel.
    10. P2-10: stale main-lock and abandoned recovery-lock reclamation now compare-delete against the originally observed owner token (or a legacy file fingerprint), so a replacement lock is not blindly unlinked.
    11. P2-11: corrupt-state backups remain ineligible for automatic deletion but now include an evidence warning and shell-quoted manual removal command.
    12. P2-12: stop-intent inspection failures no longer turn an otherwise confirmed lifecycle shutdown into a false tunnel failure.
    13. P2-13: doctor --cleanup reports eligible cleanup as skipped, rather than not-requested, when corrupt/incomplete state makes deletion unsafe.
    14. P2-14: an unknown child signal maps to a stable nonzero fallback instead of producing NaN and a secondary range error.
    15. P2-15: CLI and README wording now clarify that --remote-port selects where to find an inspector already opened with matching --inspect=<port>; SIGUSR1 cannot choose a custom port.
    P3 — maintainability and diagnostics
    1. P3-1: oversized lifecycle, startup, command-execution, process-stop, and CLI handlers were split into focused helpers without changing their fail-closed outcomes.
    2. P3-2: one-shot SSH execution, shared bounded-output/redaction helpers, and persistent-tunnel diagnostics now live in separate modules, leaving every source file below the package limit.
    3. P3-3: startup-age and recorded-process verdicts have one shared implementation for health pruning and explicit stop.
    4. P3-4: obsolete single-PID ownership and identity helpers plus the unused inspector timeout constant were removed.
    5. P3-5: readiness races dispose their losing child-close listeners instead of leaking two listeners per start.
    6. P3-6: legacy-v1 doctor output now reports home counts and defensively parsed claimed sessions with conservative PID liveness, without adopting or mutating v1 state.
    7. P3-7: missing-app failures consistently name --app or selector.
    8. P3-8: --version reads the installed package metadata at runtime, so it cannot drift when packaging updates the version after compilation.
    9. P3-9: documentation now calls out the descriptor-dependent cost of the non-GNU remote /proc fallback.
    10. P3-10: ambiguous multi-Node selection now reports the discovered PID candidates so cluster users can choose a request-handling worker with --node-pid.
    11. P3-11: automatic local-port scans start at a deterministic hash of the exact registration identity and wrap the full range, reducing concurrent first-candidate collisions while preserving preferred-port priority.
  2. v0.2.1

    Other 1

    Notes
    • Re-verify the spawned one-shot SSH process identity immediately before both graceful termination and forced escalation. If Unix process ownership can no longer be proved, cf-debugger now refuses to signal the PID, releases its local stream handles, and preserves the existing timeout/abort result.
  3. v0.2.0

    Notes

    This minor release intentionally changes observable safety behavior. Startup can now reject a tunnel that 0.1.x reported as ready, app restart is opt-in, tunnel signal death and failed cleanup use nonzero exit statuses, and new recovery commands/flags are available. State remains additive schema v2.

    1. Readiness now proves both local listener ownership and an attachable Node inspector response from /json/list. A bound forward with no working remote inspector fails as INSPECTOR_UNREACHABLE.
    2. Automatic cf enable-ssh plus cf restart is now opt-in through --allow-ssh-enable-restart or CF_DEBUGGER_ALLOW_RESTART=1. Permission, already-enabled, space-policy, and unknown-probe failures never restart the app; an allowed restart is warned with its exact target.
    3. Session recovery now detects PID reuse, bounds abandoned startup records, and provides stop --force to forget unverifiable state without signalling an unverified process. The public stopAllDebuggers() return value changes from a count to a StopAllResult containing every per-session outcome and summary count; CLI stop --all rejects any simultaneous selector instead of silently widening the requested scope.
    4. Tunnel signal death maps to 128 + signal; incomplete cleanup exits 70. Cleanup ignores unrelated listeners that later occupy the old local port.
    5. Region resolution includes all sibling-package regions and synthesizes valid new SAP keys with a warning. Malformed keys use UNKNOWN_REGION, and --api-endpoint is available for exact/nonstandard landscapes.
    6. Startup has one configurable overall deadline (--startup-timeout, default five minutes). CF retries share that budget, rejected credentials are tried once, and verbose retry messages show the remaining time.
    7. Bounded, redacted tunnel stdout/stderr tails now reach readiness and unexpected-exit diagnostics; verbose mode streams labelled redacted lines.
    8. One corrupt session no longer erases healthy records. Invalid originals are preserved as private corrupt backups, and the new read-only doctor command reports orphan homes/ports/temp files and legacy credential artifacts; doctor --cleanup performs only guarded v2 cleanup.
    9. Batch stop, Windows port parsing, multi-PID ownership, exact selectors, guarded home deletion, stream-specific truncation, macOS lsof diagnostics, configurable remote inspector ports, --version, color-free CF parsing, and immutable per-session CF_HOME behavior are corrected.
    10. Remote listener ownership avoids one fork per file descriptor, startup stop polling is reduced to a proportionate cadence, port scans no longer hold the global lock, and no-op state writes/redundant reads are removed.
    11. Dead CF table helpers are removed, bundles share split chunks, startup responsibilities are separated, foreign-host locks age out conservatively, failed status streams remain terminal at error, and selector/input errors consistently use coded failures.
0downloads per week, npm
Weekly downloads for @saptools/cf-debugger26 complete calendar weeks from 2026-04-27 to 2026-10-25; the latest total is 0.27 Apr – 3 May: 6644 May – 10 May: 19711 May – 17 May: 3318 May – 24 May: 13325 May – 31 May: 891 Jun – 7 Jun: 388 Jun – 14 Jun: 5815 Jun – 21 Jun: 1122 Jun – 28 Jun: 63029 Jun – 5 Jul: 1,2746 Jul – 12 Jul: 28913 Jul – 19 Jul: 58520 Jul – 26 Jul: 1,12927 Jul – 2 Aug: 8513 Aug – 9 Aug: 39010 Aug – 16 Aug: 41617 Aug – 23 Aug: 27224 Aug – 30 Aug: 5431 Aug – 6 Sept: 2537 Sept – 13 Sept: 10714 Sept – 20 Sept: 3021 Sept – 27 Sept: 4128 Sept – 4 Oct: 255 Oct – 11 Oct: 6312 Oct – 18 Oct: 1319 Oct – 25 Oct: 0
Debugging
  1. dbg_value_statementDebugger script generating a VALUE statement for a variablefrom ABAP
  2. dump_infoDisplays the variables of ABAP short dumpsfrom ABAP
  3. com.sap.cds:cds-feature-consoleConnects a running CAP Java app to CAP console for monitoringfrom CAP
  4. com.sap.cds:cds-feature-dev-dashboardBrowser dashboard for a running CAP Java service during developmentfrom CAP
  5. @cap-js/consoleConnects a running CAP application to the CAP consolefrom CAP
  6. @cap-js/data-inspectorInspects database entities and contents at CAP service levelfrom CAP
  7. abapsmithABAP MCP server over ADT with a local journal that can undo writesfrom ABAP
  8. sap-cap-debuggerAttaches a debugger to a CAP app running on Cloud Foundryfrom CAP
  9. ABAP-Object-VisualizerDebugger script exporting an object hierarchy as Graphvizfrom ABAP
  10. abap_debugger_object_graph_extensionABAP debugger extension drawing objects as a Graphviz graphfrom ABAP
  11. Graphviz-browserStatic page drawing Graphviz graphs for the ABAP debugger extensionfrom ABAP
  12. ABAP remote filesystemVS Code extension editing ABAP systems over ADT as a filesystemfrom ABAP
  13. @sap/cds-dkCommand line client and development toolkit for CAPfrom CAP
  14. @saptools/cf-explorerFile search in apps deployed to SAP BTP Cloud Foundry over cf sshfrom BTP/BAIP
  15. @saptools/cf-function-traceStep-by-step state recorder for one JavaScript function in Node.jsfrom BTP/BAIP
  16. @saptools/cf-hanaSQL runner against the SAP HANA Cloud database bound to a CF appfrom BTP/BAIP
  17. @saptools/cf-inspectorShell debugger for remote Node.js over the Chrome DevTools Protocolfrom BTP/BAIP
  18. @saptools/cf-live-traceHTTP trace hook streaming a running CF Node.js app's trafficfrom BTP/BAIP
  19. @saptools/cf-request-runnerEndpoint discovery for CAP services deployed to Cloud Foundryfrom CAP
  20. @saptools/service-flowStatic end-to-end tracer of one CAP operation across repositoriesfrom CAP
  21. Smart DebuggerSAP GUI debugger script recording steps so execution can run backwardsfrom ABAP

Details

Sources4
Added by handdiscovered herechecked
npm registry, @saptools/cf-debuggerversion, releases, installschecked
github.com/git+https://github.com/dongitran/saptools.gittree, pushes, contributors, CIchecked
packages/cf-debugger/README.mdquoted, and what the entry declareschecked

Sources are graph entities: the same page may back several entries, and an unreachable source flags every row that rests on it.

Compatibility and provenance
deploymentCloud Foundry
latestv0.3.0
first release
releases and cadence21, ~2d median
discovered viaAdded by hand
first seen in the atlas
ecosystem and domainbtp, developer-experience
History in the atlas1
  1. First seen.
For your agent
Ref
artifact:saptools-cf-debugger
JSON
/artifacts/saptools-cf-debugger.json
MCP
Connecting an agent