ChannelGate (formerly Claude Gateway for Slack) is a self-hosted daemon that runs coding agents inside a container per conversation. Contributions are welcome, and because the gateway executes model output on someone's machine, review is strict about isolation, secret handling, and cross-platform behaviour. Everything below is what a reviewer will actually check.
By contributing you accept the Contributor License Agreement — the Signed-off-by
trailer on each commit is that acceptance (CLA Section 7). Participation is governed by the
Code of Conduct.
Requirements: Node >= 22.13 (the floor is exact — node:sqlite is stable from 22.13 and the
daemon refuses to boot below it), Git, and Linux — the daemon refuses to boot anywhere else.
git clone <your fork> && cd <checkout>
npm ci
cp .env.example .env # full live setup also needs rootless Podman + provider API credentials
npm start # admin UI + health endpoint on http://localhost:4747.env is gitignored and never committed. Runtime state (config, sessions, logs, the SQLite
database) lives outside the repository under the gateway runtime root, not in the checkout.
INSTALL.md covers a full working install; the tests below need neither Slack credentials nor a
running daemon.
npm test # full node:test suite (test/*.test.js)
npm run test:coverage # the same suite with the enforced coverage floors — what CI runs
npm run check:static # syntax, undeclared identifiers, whitespace invariants
npm run secret-scan # dependency-free credential scan over tracked files
npm run check:dco -- origin/beta..HEAD # every development commit carries a sign-off trailernpm run test:security-coverage enforces the extra coverage floors on the security-critical
modules; run it when you touch authorization, confinement, secrets, or the admin API. A single file
runs with node --test test/<name>.test.js.
Tests must leave no scratch directories behind. Create temp directories with tempDir(prefix) from
test/helpers.js (never a bare mkdtempSync(path.join(os.tmpdir(), …))) — it registers them for an
exit-handler cleanup, and npm test also runs scripts/test-scratch-sweep.mjs first to sweep
anything a crashed run left in the system temp dir.
Every one of these must pass before you open a pull request. CI runs the suite on Node 22.13 and 24 on ubuntu-latest.
Use your own fork, branch and isolated worktree. Start from the latest upstream beta, make focused
commits and open a PR targeting beta with validation results. You do not need access to the publisher's GitHub
account, private QA database or production host. Maintainers handle final integration and release.
beta receives all completed development work. main is the stable update branch. A release PR
from beta to main requires the full test and live acceptance evidence defined in AGENTS.md,
followed by explicit user confirmation for that exact tested candidate. Beta work is not
authorization to release; required checks that are blocked or unexecuted prevent promotion.
Keep FEATURES.md and TEST-PLAN.md consistent with behavior. Include exact fixtures, setup,
prompts/actions and pass rules for applicable Claude and Codex acceptance. Report live cases you
could not execute explicitly so maintainers can run them; a local unit test is not a live pass.
Never resolve a shared document conflict by discarding another contributor's evidence.
Deployments that serve a checkout directly may require serialized landing using
npm run with-landing-lock -- <command>. Follow that host's operator instructions; ordinary
contributors should not deploy or push directly to the upstream main branch.
- Sign off every commit:
git commit -s. TheSigned-off-by: Your Name <you@example.com>trailer is how you acceptCLA.md; a real name and a reachable address are required, and pseudonymous sign-offs cannot be accepted. Already committed without it? `git rebase --signoff `. CI rejects the pull request otherwise (`scripts/check-dco.mjs`). - Conventional, imperative subject, present tense, no trailing period:
fix: reject an unsigned commit range, notfixed stuff. - One change per commit. A refactor and a behaviour change are two commits.
- Stage only your own files —
git add <path>, nevergit add -A/git commit -a. If the tree holds edits you did not make, leave them unstaged. - Explain why in the body. The diff already says what.
Open a pull request directly for:
- Bug fixes with a regression test that fails before the fix.
- Documentation corrections and clarifications.
- Tests covering existing behaviour, especially the security-critical modules.
- Platform adapters and engine runners that follow the registry pattern described in
AGENTS.md: a chat surface is an adapter plus a connector insrc/platforms/with a complete capability descriptor; an engine is a runner plus an entry insrc/engines/registry.js. Adding aplatform === "slack"orengine === "codex"branch anywhere else is the thing those registries exist to prevent, and it will be sent back.
Ask first — open an issue or a discussion before writing code — for:
- new license tiers, limits, or anything touching license-key enforcement;
- anything under
src/ee/, which is proprietary, source-visible code owned by MAKEITFUTURE S.R.L. and is not under the Sustainable Use License — its own terms are insrc/ee/LICENSE-EE.md, andLICENSE.md§3.2/§4.5 make removing, disabling, or circumventing the key verification, the usage limits, or the usage reporting a licence violation. We are happy to take bug reports and security findings about it; we cannot take patches to it without a prior discussion and a signed CLA covering that directory. The same applies to the enforcement call site insrc/gateway/run.js(licenseAdmission()) and to thelicense_usageschema; - licensing, CLA, or trademark text (
LICENSE.md,CLA.md,TRADEMARK.md,docs/LICENSING-*); - new runtime dependencies — the project is deliberately close to dependency-free outside the Slack, MCP, and Composio SDKs;
- schema changes (migrations are append-only; an existing migration is never edited);
- anything that changes the security model.
Put a short design note in the pull request description: the problem, the option you chose, the
options you rejected, and how it is tested. That is the durable record for a reviewer. Larger
design documents, roadmaps, and audit notes are kept privately by the maintainers and are not part
of this repository — do not add a docs/plans/ or a working-notes file to a pull request.
Behaviour changes update TEST-PLAN.md (the cumulative regression list) and, when they ship a
user-visible capability, FEATURES.md and CHANGELOG.md.
Do not open a public issue, discussion, or pull request for a suspected vulnerability. Report it
privately per SECURITY.md — contact@makeitfuture.com, with the affected version,
reproduction, and impact, and no live credentials or customer data. Rotate anything that may have
leaked before you report.
These are not style preferences; a change that breaks one of them does not merge.
- Confinement is the product, and the container is the boundary. Every turn runs inside the
channel's own container: a per-channel HOME volume, only the work folder (plus its clean
workspace and artifact dir) mounted, no host home or gateway root by default. The explicit Full-access whole-home option widens
this boundary and must remain off unless deliberately selected by the operator. Every channel
folder still gets the lockdown settings — automatic memory off, curated permission allowlist,
the MCP allowlist — as policy, never a
sandboxblock. An engine is never exec'd outside a container, and nothing a run can do changes what its container mounts. The boundary is filesystem and process isolation, not egress: Allow network is a per-channel switch the engines are told about, not a filter. - Secrets never ride a listing response. Listing endpoints return
has*andlast4only. A value is fetched one at a time through the reveal endpoint, which re-checks the admin password and audit-logs what was revealed, never the value. New secret fields go in the reveal allowlist. Nothing is hardcoded and nothing is logged. - Linux only. ChannelGate targets Linux with systemd and rootless Podman: no macOS/launchd
branches, no BSD-tool assumptions — and still no
setsidor other platform-specific binaries; prefer portable Node APIs over shelling out and keep shell POSIX-portable. - A run is never killed for being quiet, and a waiting state always announces itself. Silence that looks like death is the bug the heartbeat and watchdog exist to prevent.
- Platform capabilities are declared once and fail closed. Read them through the capability helpers; an undeclared capability resolves to the least capable value.
- Only admins get skipped permissions, and authorization is checked before anything else runs.
Usage and troubleshooting: SUPPORT.md. Licensing, commercial use, and partner
inquiries: contact@makeitfuture.com and docs/LICENSING-FAQ.md.