Skip to main content

Activation: auto-connect vs dormant-by-default

ContextRelay has to answer one question every time a Claude or Codex session starts in a project: should I be active here? If the answer is yes, the bundled hooks surface ContextRelay (the collaboration ruleset, the "Codex is working" nudges, the handoff prompts). If the answer is no, ContextRelay stays quiet until you explicitly invoke it.

This page explains the two baselines (auto-connect and dormant-by-default), the per-session opt-in, and - most importantly - the exact precedence chain that resolves the answer. The chain is small but subtle, so it is worth understanding before you flip any flags.

The default: auto-connect ON

Out of the box, ContextRelay ships auto-connect ON. When you install it and start a session in a project, the bundled SessionStart and UserPromptSubmit hooks fire and surface ContextRelay automatically - no extra step. This is the friendliest default for the common case: you set up ContextRelay because you want two agents collaborating, so it engages without ceremony.

Auto-connect ON is also the fail-open baseline. If anything about activation resolution goes wrong, ContextRelay treats the session as active rather than silently disappearing - a bug in the gate must never break a collaboration loop you were relying on.

Going dormant: ctxrelay standalone on

Auto-connect is great when ContextRelay is installed because you want it everywhere. But if ContextRelay is installed globally and you only want it in some sessions, having it nudge in every project is noise. That is what dormant-by-default (standalone mode) fixes.

ctxrelay standalone on

This does three things:

  1. Writes the global activation flag to ~/.contextrelay/activation.json ({ "autoConnect": false }), making ContextRelay dormant for that scope.
  2. Slims the always-on instruction copies. It replaces the full collaboration ruleset with a short dormant notice in the conventional home/global CLAUDE.md and AGENTS.md files (~/CLAUDE.md, ~/AGENTS.md, ~/.claude/CLAUDE.md, ~/.codex/AGENTS.md) - but only in files that already carry a managed ContextRelay block.
  3. Installs a bare /contextrelay user command at ~/.claude/commands/contextrelay.md, so any session can opt back in with a single slash command.

After this, new sessions are quiet. Nothing nudges until you opt a session in.

To return to auto-connect:

ctxrelay standalone off

That restores autoConnect: true, re-applies the full instruction block to the slimmed files, and removes the bare /contextrelay command.

To inspect the resolved state and find which files carry slim vs full blocks:

ctxrelay standalone status

Standalone flags

FlagDefaultMeaning
--scope project|global|bothglobalWhere the activation flag is written. project writes activation.autoConnect into .contextrelay/config.json; global writes ~/.contextrelay/activation.json; both writes both.
--path <file>-Also slim/restore this specific CLAUDE.md/AGENTS.md (repeatable). Use it to target parent-folder copies that standalone status surfaces.
The current repo's committed instruction files are never slimmed by default

standalone on deliberately skips the working repo's own committed CLAUDE.md/AGENTS.md so they stay full (this keeps the project's instruction-sync check green). If you pass one explicitly with --path and it lives inside a git repo, the command warns you, because committing that change could affect that repo's CI.

Opting a single session in: attach / detach

Dormant-by-default is the baseline; attach is the per-session override. It forces ContextRelay active for the current workspace regardless of the dormant baseline:

ctxrelay attach # opt this workspace in
ctxrelay attach review # opt in and bind a named session

attach is deliberately robust - it never depends on how Claude was launched. It:

  1. Ensures the daemon is running (so the bridge and registry are reachable).
  2. Optionally binds a named session in the registry when you pass a name.
  3. Writes a per-workspace activation marker so the gated hooks re-engage on your next prompt.
  4. Prints the full collaboration ruleset to stdout, so it lands back in the conversation even when the always-on block was slimmed.
  5. Prints how to start the peer agent (ctxrelay codex, or ctxrelay pair from a fresh shell).

To opt back out:

ctxrelay detach

detach removes the activation marker so the hooks stop nudging from the next prompt (when the baseline is dormant). It is idempotent and intentionally narrow: it does not stop the daemon or Codex. For that, use ctxrelay kill.

Slash commands are the easy path

Inside a Claude Code session you do not have to type the CLI. Run /contextrelay:on to attach and /contextrelay:off to detach - they run ctxrelay attach / ctxrelay detach deterministically. When ContextRelay is dormant-by-default, the bare /contextrelay command (installed by standalone on) also runs attach.

Where the marker lives

The marker is a file named <workspace-key>.on under <stateDir>/activation/, where the workspace key is the first 16 hex characters of sha1(resolved project directory). The marker is written to the default state directory (honoring CONTEXTRELAY_STATE_DIR), which is the same place the hooks look - not the daemon's project-local state dir.

The precedence chain (highest to lowest)

Here is the part worth memorizing. Activation is resolved by a single pure function (resolveActivation in src/activation.ts) that walks these tiers in order and returns at the first one that gives a definite answer:

#TierWins whenResult
1Env CONTEXTRELAY_AUTO_CONNECTSet to a recognized tokenTruthy (1/true/yes/on) → active; falsey (0/false/no/off) → dormant
2Per-session marker (attach)A marker file exists for this workspaceactive
3Project config activation.autoConnect in .contextrelay/config.jsonExplicitly set (true or false)Takes that value
4Global config autoConnect in ~/.contextrelay/activation.jsonExplicitly set (true or false)Takes that value
5Shipped defaultNothing above appliedactive (auto-connect ON)

A few consequences fall out of this ordering:

  • The env var beats everything. CONTEXTRELAY_AUTO_CONNECT=1 forces a session active even if you went dormant globally; CONTEXTRELAY_AUTO_CONNECT=0 forces a session dormant even if a marker is present. It is the per-invocation escape hatch.
  • attach beats config, not env. A per-session attach marker re-activates a dormant workspace, but CONTEXTRELAY_AUTO_CONNECT=0 in the environment still wins over the marker.
  • Project config beats global config. A per-project activation.autoConnect (set with standalone on --scope project) overrides the global flag, so you can keep a project active even while everything else is dormant - or vice versa.
  • Unset tiers are skipped, not treated as false. The resolver only stops at a config tier when the value is explicitly present. An absent flag falls through to the next tier, ending at the shipped default of active.
The hooks and the Python hook share this resolution byte-for-byte

The workspace key (sha1(project dir)[:16]), the marker layout (<stateDir>/activation/<key>.on), the global flag path (~/.contextrelay/activation.json), and the precedence order are an internal parity contract shared in lockstep between src/activation.ts and the bundled peek_codex_queue.py hook. Both sides must change together. This is an implementation invariant you should not depend on changing.

How do I check whether I'm active?

Use gate-check. It resolves activation for the current session using exactly the chain above - pure file/env/marker reads, no daemon or WebSocket calls, so it is cheap enough to run from a hook:

ctxrelay gate-check --why
# active - default (auto-connect on)
# ...or...
# dormant - global config autoConnect=false
FlagOutput
(none)No stdout; the exit code carries the answer
--statusactive or dormant
--json{"active":true,"reason":"..."}
--whyOne line: active - <reason> / dormant - <reason>

The exit code is the contract: 0 means active (hooks should run), 1 means dormant (hooks no-op). The bundled SessionStart and UserPromptSubmit hooks consult this same resolution before nudging you, so gate-check tells you precisely what they will decide.

gate-check fails open

If anything unexpected goes wrong inside the gate, it exits 0 (active). A crash in the gate can never silently suppress the collaboration loop - the worst case is that ContextRelay engages when you might have expected it not to, which is recoverable, rather than vanishing when you needed it.

standalone status is the higher-level companion: it shows the resolved state plus the project flag, the global flag, the bare-command status, and which instruction files carry slim vs full blocks.

Putting it together: typical flows

"I want ContextRelay everywhere" - do nothing. Auto-connect ON is the default.

"I installed it globally but only want it sometimes":

ctxrelay standalone on # go dormant everywhere
# ...later, in a project where you want both agents:
ctxrelay attach # or run /contextrelay in a Claude session

"This one project should always be active, but keep the rest quiet":

ctxrelay standalone on # dormant globally
ctxrelay standalone off --scope project # but active for this project

"Just for this command, override whatever the config says":

CONTEXTRELAY_AUTO_CONNECT=1 ctxrelay claude # force active
CONTEXTRELAY_AUTO_CONNECT=0 ctxrelay claude # force dormant

Activation and upgrades

When you update ContextRelay and run ctxrelay upgrade, your activation state is preserved. The upgrade reconciler refreshes managed instruction blocks while keeping each file's slim/full state, and it never changes the auto-connect vs dormant baseline - so an upgrade will not silently re-activate a session you deliberately put to sleep. See Upgrading ContextRelay for the full flow.

Next steps