# Csharprepl Connect

> Connect to a running, connector-enabled .NET process with `csharprepl connect <pid>` and evaluate C# *inside* it — read and modify its live objects, statics, and DI services, and detour live methods (#replace/#wrap). Use to debug or probe a real running app's in-memory state, not static API surface (that's dotnet-inspect) or a throwaway snippet in a fresh process (that's csharp-eval). Dev/diagnostics only — code runs with the target's full privileges, never point it at production.

- Skill: `waf/csharprepl-connect` (Agent Skill)
- Install (CLI): `npx skillmds@latest add waf/csharprepl-connect`
- Raw SKILL.md: https://api.skillmd.com/api/skills/waf/csharprepl-connect/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: waf (https://skillmd.com/u/waf)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/waf/csharprepl-connect

---


# csharprepl-connect

Evaluate C# **inside a separate, already-running .NET process** and see/modify its live state. `csharprepl`
injects a real Roslyn engine into a target you launched with the connector enabled; you then send code
non-interactively (same flags and output as the local REPL) and it runs in that process against its actual
in-memory objects.

## When to use this vs. csharp-eval vs. dotnet-inspect

- **"What's the live state of my running app?"** / **"Change a method's behavior in the running process"**
  → **this skill** (`csharprepl connect <pid>`). Code runs *inside* the target.
- **"What does this code do?"** (a self-contained snippet, fresh throwaway process) → **csharp-eval**.
- **"What does this API look like?"** (signatures, members, docs — no execution) → **dotnet-inspect**.

The eval mechanics here — `-e` / `--eval-file`, piped stdin, quoting, `-r "nuget: ..."`, the clean-stdout /
errors-to-stderr / nonzero-exit contract — are **identical to csharp-eval**; see that skill for those
details. This skill covers only what's different about connecting to a live process.

## ⚠️ Safety

Evaluated code runs with the **target process's full privileges** — it's RCE-equivalent for same-user code.
Only connect to a process **you control** for development/diagnostics. **Never enable the connector on, or
connect to, a production process.**

## 1. Enable the target (one-time, at launch)

The target only accepts connections if it was *started* with the connector hook — you cannot enable an
already-running process. `connect init` prints the env vars to set in the shell that launches it:

```
csharprepl connect init        # prints DOTNET_STARTUP_HOOKS=... and ASPNETCORE_HOSTINGSTARTUPASSEMBLIES=...
                               # auto-detects your shell; override with --shell bash|pwsh|cmd|fish
```

Set those env vars in the launching shell only (not system- or user-wide), then start the app normally
(e.g. `dotnet run`). It's now connectable for the life of that process.

## 2. Connect and evaluate

```
csharprepl connect list                                       # list connector-enabled processes + their PIDs
csharprepl connect <pid> -e 'System.Environment.ProcessId'    # -> the target's PID; confirms code runs in the target
csharprepl connect <pid> --eval-file probe.csx                # multi-line, same as local
echo 'SomeApp.Program.SomeStatic' | csharprepl connect <pid>  # piped stdin works too
```

- **Reach the target's state** by fully-qualified name (`MyApp.Program.SomeStatic`), or, when its DI provider
  was captured, via `services.GetRequiredService<T>()` / `Get<T>()` (the connect banner reports whether the
  DI provider was captured).
- **State persists across calls** (unlike local `csharp-eval`, where each run is a fresh process): the target
  holds the script-state chain, so a `var` declared in one `connect <pid> -e` invocation is usable in the
  next. This lets you build up state with one-shot calls.

## 3. Live method replacement

While connected you can detour a live method to a REPL-defined delegate, changing the running app's behavior
immediately:

- `#replace <Type.Method> with <delegate>` — swap the implementation.
- `#wrap <Type.Method> with <delegate>` — keep the original, callable via an `orig` first parameter.
- `#patches` — list active patches; `#revert <id>` / `#revert all` — undo them.

Instance methods take the instance as the first delegate parameter; a static method omits it. Define the
helper in one call, then `#replace` in the next — they share state across calls:

```
csharprepl connect <pid> -e 'decimal Half(MyApp.OrderService svc, int qty, decimal unit) => qty * unit * 0.5m;'
csharprepl connect <pid> -e '#replace MyApp.OrderService.CalculatePrice with Half'
csharprepl connect <pid> -e '#patches'        # list active patches
csharprepl connect <pid> -e '#revert all'     # undo them
```

- A command (`#replace`/`#wrap`/`#patches`/`#revert`) must be the **whole** submission — don't combine a
  definition and a command in one `-e` or one piped block, since collected stdin is sent as a single C#
  submission (so `#replace` would be compiled as invalid C#). To do it in one pipe, use `--streamPipedInput`,
  which evaluates line by line.
- Patches **persist in the target until reverted** (or it exits) — they outlive your disconnect, so revert when
  done.
- Not supported: generic methods, pointer params, and `#wrap` with by-ref parameters.

## Gotchas

- **Can't connect if not enabled.** `connect <pid>` fails unless the target was launched with the env vars
  from `connect init` (step 1). Use `connect list` to see what's actually connectable.
- **Self-contained single-file targets are rejected** — their assemblies are bundled in memory with no
  on-disk path, so the engine can't compile against them. Connect to a framework-dependent build instead. (A
  *framework-dependent* single-file app connects but can only reach its own types via reflection.)
- **Disconnecting leaves the target running.** `connect` exits cleanly; the process keeps going and you can
  reconnect — but any patches you applied stay in effect until reverted.
- Everything else (quoting, NuGet refs, errors→stderr, exit codes) works as in **csharp-eval**.

