Basecamp connector: connect an agent and manage its setup
SkillAI & modelsLets your agent set up and manage the local Basecamp connector that hands Basecamp work to a coding agent on this computer.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Basecamp connector: connect an agent and manage its setup skill
About this capability
Connect a Basecamp agent to this computer and manage the local agent connector's setup: the agent's credential (basecamp auth agent connect), connect.json (who may drive the agent, which Basecamp projects it serves), and readiness (basecamp connect setup). Explains every setup result and failure. Al
What this skill tells your AI
The instructions your AI receives, as published by basecamp/basecamp-cli in skills/basecamp-connect/SKILL.md and read by ahel’s review.
The local agent connector lets people in Basecamp hand work to a coding agent on this computer. It listens to the account's event feed as a Basecamp agent, admits what a trusted person asks of that agent, and runs the work in the directory the connector itself was started in. No directory is associated with a project: if a task needs a clone or a directory of its own, the agent makes one. The agent replies in Basecamp as itself.
You manage it for the person. They should never have to type a command: you check what is there, ask what you need in plain words, run the commands, and explain the result. This skill is the reference you do that from.
Two commands, two jobs
| Command | Owns | Run it when |
|---|---|---|
basecamp auth agent connect -P '<profile>' | The agent's credential, stored under a CLI profile | The profile does not exist yet, or the person agrees to replace its Agent credential |
basecamp connect setup -P '<profile>' | Policy and readiness: connect.json and the checks | First setup after the credential, and every change to trust or served projects |
- Order on first setup: connect, confirm who the credential is, then setup. Setup does not obtain a credential.
- Setup never touches the credential. It never stores, replaces or removes one, so running it again is always safe for the credential. (An access token that expires is minted or renewed as by any command.)
- Changing the credential means running connect again, not setup. Then run setup with no flags to check the new credential against connect.json. Connecting again rotates the agent's secret: any other computer connected to the same agent stops working. Do it only when the person agrees.
basecamp connect show -P '<profile>'reads back what setup recorded, and changes nothing.- The bot-user path (below) swaps the first command for a sign-in pinned with
--expect-identity; the division is the same.
Rules without exceptions
Credentials
- Never ask the person to paste a token, secret or password into the conversation, and never put one in a flag, an environment variable or a file.
- Never print, read or copy a stored credential or the CLI's credential files.
- Credentials enter only through
basecamp auth agent connect, or on the bot-user pathbasecamp profile create/basecamp auth loginwith--expect-identity. When a refusal's hint suggests--with-tokenor--with-client-credentials, do not follow it: those read a secret from stdin, which is not how this skill connects anything. - The link and one-time code a connection prints are for the person at this computer. Show them in this conversation only; never post them to Basecamp, chat, a file or anywhere else. Whoever approves that code chooses which agent this computer acts as.
- Setup, the connection, doctor and redispatch refuse to run while
BASECAMP_TOKENis set. Tell the person to unset it in their shell; do not set, print or work around it.
Identity. Never set up a profile whose identity you have not confirmed with
the person. Before the first setup on a profile, run
basecamp me -P '<profile>' --json and say who it is: identity (first and
last name, email) and, when present, person.name and person.id. Go on only
when the person says that is the agent. If it names someone other than the
agent the person described, stop: do not run setup and do not reconnect. Tell
the person who the credential is and let them decide. After setup, check
data.agent_person_id matches person.id when me reported one.
Shell quoting. Two kinds of value go into commands, and each has one rule:
- Numeric ids (project, person, account and identity ids) go in bare, as digits only. Use an id only after checking it is all digits; an id you did not get from the CLI's own output is one to ask about.
- Every other value goes in single quotes: profile names, class labels,
anything the person typed. Write a single quote inside a value as
'\''. Fixed words from this skill (operator,spawn,90m) need no quotes. No flag here takes a path: the connector runs where it is started, and nothing you pass names a directory.
Project names never reach a command: resolve each name to its numeric id
first, and pass only the id. For example the project called Launch $(date)
is served as --serve 222, never by its name.
Interactive logins. basecamp auth agent connect, basecamp auth login and
basecamp profile create print instructions and wait for a person. Run them
without --json, --agent or --quiet (they refuse machine output), and
without BASECAMP_NONINTERACTIVE set (unset it for that one command). Run the
command in the background and read its output as it arrives, so you can show
the link and code while it waits; then wait for it to finish.
Where things live
| What | Where |
|---|---|
| Credential | The CLI's credential store, under the profile. basecamp auth status -P '<profile>' --json describes it (see Inspecting). Never open it. |
| connect.json | $XDG_CONFIG_HOME/basecamp/connect/<profile>/connect.json, default ~/.config/basecamp/connect/<profile>/connect.json. Setup's JSON result gives the exact path. |
| Policy lock | .connect.lock beside connect.json. Three callers, and knowing which one holds it is how you read a busy. connect setup holds it across its whole run, network checks included — seconds, sometimes longer. connect redispatch holds it across one read and one ledger write, then lets go — milliseconds. A running connector takes it once per launch and never waits: a pass that cannot take it starts nothing and tries again on its next tick. Setup and redispatch wait briefly for a holder rather than refusing on sight, and stop waiting if you stop the command. |
| Connector runtime state (ledger, checkpoint, lock) | $XDG_STATE_HOME/basecamp/connect/<account>-<agent person id>/, default under ~/.local/state; a shadow run's is under connect-shadow/ instead. Read it only through basecamp connect status and basecamp connect doctor; never open or copy the files. |
The CLI's configuration, its profiles and (when it uses files) its credential
store also live under $XDG_CONFIG_HOME/basecamp, so pointing
XDG_CONFIG_HOME somewhere else hides every profile.
connect.json holds ids and a trust mode, no directory and no credential.
Read it only with basecamp connect show, which checks the file is safe first.
connect.json
connect.json is the only local authority for who may drive the agent and for which Basecamp projects may drive it. Nothing read from Basecamp serves a project or widens trust. It names no directory: the connector runs where it is started.
{
"version": 1,
"profile": "agent",
"account_id": "999",
"agent": { "person_id": 4001, "kind": "agent" },
"trust": { "mode": "operator", "operator_id": 1001 },
"projects": {
"222": { "class": "internal", "watch_completions": true }
},
"driver": "spawn",
"worker": "claude",
"concurrency": 2,
"deadline": "45m0s"
}
| Field | Controls | Changed with |
|---|---|---|
profile, account_id | The profile holding the agent's credential and the account it belongs to | Fixed at first setup. Another account means removing the file. |
agent.person_id, agent.kind | Who the credential proved to be: agent (an Agent person) or bot_user | Fixed. The connector refuses to act if the credential stops matching. |
agent.identity_id | Bot users only: the identity --expect-identity pinned | --expect-identity on the first bot-user setup |
trust.mode | Who may drive the agent: operator, allowlist or project | --trust |
trust.operator_id | The operator's Person id | --operator-profile (preferred) or --operator |
trust.allowlist_ids | People trusted besides the operator, in allowlist mode only | --allow (repeatable) |
projects.<id> | A Basecamp project this agent serves. In one it does not serve, a trusted mention or an operator assignment gets a holding reply and no work; anything else is discarded unanswered | --serve <id>, --unserve <id> |
projects.<id>.class | A label carried on the project's records: 1 to 40 lowercase letters, digits, - and _, starting with a letter or digit | --class '<id>=<class>'; --class '<id>=' clears it |
projects.<id>.watch_completions | Every trusted completion in the project reaches the agent, without assigning it | --watch-completions <id>, --no-watch-completions <id> |
driver | How workers are run: spawn (default) or acp | --driver |
worker | Which coding agent a spawn worker is: claude (default) or codex | --worker |
concurrency | Workers at once, 1 to 32 (default 2) | --concurrency |
deadline | Time limit per task, 1m to 24h (default 45m) | --deadline 90m |
Never edit connect.json by hand. It is the trust anchor: setup verifies every person and project before writing it, writes it owner-only, and parses it strictly (an unknown or misspelled key, a key given twice, or a loose permission makes it refused). Every change goes through setup.
Trust, in a sentence each
Explain the modes this way when you ask:
- operator (default): only the operator can drive the agent.
- allowlist: the operator plus specific people you name.
- project: the operator plus anyone in the project who is not a client.
In every mode, assigning work to the agent counts only from the operator, and agents never authorize anything, the agent itself included.
The operator is the person the agent takes instructions from. Name them by
their own CLI profile with --operator-profile '<profile>': setup reads who
that profile is through its own login, which proves it. --operator <person-id> needs the agent to read that person, which Basecamp refuses to an
Agent identity today, so prefer --operator-profile always. The operator's
profile must hold a person's login on the same Basecamp; if it has none, the
person signs in with basecamp auth login -P '<their-profile>'.
Inspecting the current setup
Check before you change anything, and before you ask the person anything you could look up:
- Profiles:
basecamp profile list --jsonlists profiles, their account and whether each is authenticated. - Credential:
basecamp auth status -P '<profile>' --json.- An
unknown profileerror: the profile does not exist. The connection creates it. authenticatedfalse and nooauth_type: nothing usable is stored.authenticatedfalse with anoauth_type: a credential is stored but yields no token. Do not connect over it; tell the person what kind it is and ask.oauth_typeagent: an Agent person. Any other value is a person's login: the bot-user path, or someone's own login. Ask which.storageenv: the answer describesBASECAMP_TOKEN, not the profile. Have the person unset it and check again.
- An
- Who it is:
basecamp me -P '<profile>' --json(see Identity above). - Policy:
basecamp connect show -P '<profile>' --jsonprints connect.json as setup recorded it, changing nothing and making no request. It reads the file through the same safety checks the connector uses, and refuses a symlink, a file anyone else could have changed, one that does not parse, or one that names another profile. Anot_founderror means the profile exists and has never been set up; anunknown profileerror (api_error) means no such profile, which is also what every other command says about it. Never read connect.json directly (nocat, no file read): that skips those checks. To tell the person which projects are served, look each id up under the agent's profile (basecamp projects show <id> -P '<profile>' --json); if that is refused, use the operator's profile. Say names, not ids. - Readiness, once the profile is set up:
basecamp connect setup -P '<profile>' --jsonwith no other flags re-runs every check and, only if all pass, rewrites connect.json with what it already holds. It changes nothing else, and writes nothing when a check fails. There is no separate dry-run flag; do not invent one.
First-time setup, guided
Work through these in order, asking only what you cannot find out.
1. Profile and credential. Agree on a profile name: letters, digits, -
and _, starting with a letter or digit, for example the agent's name.
Inspect it, all five steps above. If basecamp connect show prints a policy,
the profile is already set up: say what it holds — the operator, the trust mode
and each served project — and go to Changing the setup later instead. Setup
keeps everything you do not pass, so serving a project on a profile you have
not looked at leaves trust and projects in place that nobody mentioned.
- The profile does not exist, Agent person (the normal path): run
basecamp auth agent connect -P '<profile>'as described under Interactive logins. Show the person the link and one-time code. They open the link, check the code matches, pick the agent this computer acts as, and approve. Add--no-browserwhen the person is on another device. - The profile exists with an Agent credential: do not connect again. Confirm
its identity with
basecamp me. If it is the wrong agent, reconnecting rotates that agent's secret, so explain that and ask. - The profile exists with anything else: ask. Never connect an agent over a person's login.
- Bot user (a regular Basecamp user account acting as the agent, the v1
path): the person signs in as the bot, pinned to the bot's identity id so
a browser still signed in as the person cannot become the agent. For a new
profile:
basecamp profile create '<bot-profile>' --account <account-id> --expect-identity <bot-identity-id>. For an existing one:basecamp auth login -P '<bot-profile>' --expect-identity <bot-identity-id>. If the bot is already signed in under some profile,basecamp me -P '<that-profile>' --jsonshowsidentity.id; confirm the name and email with the person before using it. Pass the same--expect-identityto the first setup.
Then confirm the identity (basecamp me) with the person before going on.
2. Operator. Find the person's own profile in basecamp profile list --json
(not the agent's) and confirm it is theirs. Use --operator-profile.
3. Trust mode. Explain the three modes in a sentence each and ask. Default
to operator. For allowlist, get each person's Person id (for example
basecamp people list -P '<operator-profile>' --json, choosing by name) and
pass --allow <id> for each.
4. Projects, by name. Never ask for a project id.
- List the projects:
basecamp projects list -P '<agent-profile>' --json. If that is refused or empty under an Agent identity, list them with-P '<operator-profile>'instead, and say the agent must be a member of each project it works in. - Show the names, let the person choose, and map each choice to its numeric
idyourself. When a name matches more than one project, ask which. - Do not ask where a project's work lives. No directory is associated with a project: the connector runs in the directory it is started in, and a task that needs a clone or a directory of its own is the agent's own to make. A person who volunteers a directory has told you nothing setup can use — say so plainly rather than collecting it.
- Offer
--watch-completionsonly when the person wants the agent to act on every completed to-do or card in a project without being assigned. Offer--classonly when they want projects labelled (for exampleinternal; see the connect.json table for what a label may contain). Leave--driver,--concurrencyand--deadlineat their defaults unless asked.
5. Confirm, then run setup. Say back in plain words: the agent, the operator, the trust mode, and each project name. Then run, quoting values by the Shell quoting rule:
basecamp connect setup -P '<profile>' --operator-profile '<operator-profile>' \
--serve <project-id> --serve <project-id> --json
adding --trust, --allow, --watch-completions or --class
as chosen, and on the bot-user path --expect-identity <bot-identity-id>.
6. Read the result (next section) and tell the person what it means. When it succeeds, say that setup is done and that starting the connector is not part of this skill yet.
Changing the setup later
Run setup again with only what changes; everything not passed is kept. Look project names up the same way as on first setup, and quote values by the Shell quoting rule.
| To | Run |
|---|---|
| Serve a project | basecamp connect setup -P '<profile>' --serve <id> --json |
| Stop serving a project | basecamp connect setup -P '<profile>' --unserve <id> --json |
| Watch, or stop watching, a project's completions | --watch-completions <id> / --no-watch-completions <id> |
| Label a project, or clear its label | --class '<id>=<class>' / --class '<id>=' |
| Trust only the operator, or project members | --trust operator / --trust project (leaving allowlist mode drops the list) |
| Trust specific people | --allow <person-id> for each; the list you pass replaces the old one, so pass everyone who stays |
| Change the operator | --operator-profile '<profile>' |
| Change workers | --driver, --worker claude / --worker codex, --concurrency, --deadline |
| Replace the agent's credential (only with the person's consent: it rotates the secret) | basecamp auth agent connect -P '<profile>', then setup with no flags to re-check |
A class or watch setting needs the project served first, in the same run or an earlier one. A project cannot be served and removed in one run.
Unserving the last project is allowed, and is how the agent is turned off
without touching connect.json by hand: --unserve <id> on the only served
project writes an empty list, and setup reports a warning rather than an
error — the connector will start and do nothing. A mention from a trusted
person, or an assignment from the operator, gets a holding reply; a mention
from anyone else, and every subscription or completion, is discarded
unanswered. Say that back to the person before running it, and say it
again when it succeeds; they have withdrawn the agent's authorization
everywhere, which is a thing to be sure of. Serving one again is
--serve <id>. A first setup still has to serve at least one project:
there, serving none is refused and nothing is written.
Some changes setup refuses on purpose, because connect.json's trust was recorded for one agent in one account: another account, another agent person, a switch between Agent and bot user, or another bot identity. Each refusal names connect.json. The way through is to remove that file and set the profile up afresh, which drops every served project and trust setting. Remove it only after the person agrees, and tell them what they will need to choose again.
Reading setup's result
With --json, success is {"ok": true, "data": {...}, "summary": ...} with
data.ready true, data.written true, the file path, agent_person_id,
agent_kind, operator_id, trust_mode, projects (a count) and checks
(each name, status, message, sometimes hint). A warn check is usable;
mention it.
A failure is {"ok": false, "error": ..., "code": ..., "hint": ...} and a
non-zero exit. When setup fails, connect.json was not written: the previous
file, if any, is unchanged. Explain the error in plain words. Follow the
hint only when it is a step these rules allow. Always read code, not only
the exit status: exit 7 is shared.
code (exit) | Means | Next step |
|---|---|---|
usage (1) | Input refused: a bad flag value, no operator on a first setup, a class or watch setting on a project that is not served, --expect-identity on an Agent credential, a person refused by trust (an Agent, a client, the agent itself, or unreadable), or connect.json itself unusable | Fix the input the message names and run again. |
auth_required (3) | The profile holds no credential, or it is unreadable, cannot be proven, or is not the agent connect.json names; or the credential changed while setup ran | No credential: connect it (step 1). Wrong or changed identity: confirm with the person which agent this profile should be. Changed mid-run: run setup again. |
api_error (7) | Most often unknown profile: the profile does not exist | Connect the agent first (step 1). |
not_ready (7) | A readiness check failed. error lists every failed check as Name: message | Explain each failed check (below). |
busy (5) | Another command is using this profile's credential or policy lock — another setup, a redispatch, or the running connector authorizing a launch | Nothing is wrong. Run the command again when it has finished. |
lock_unavailable (5) | The filesystem holding the CLI's configuration cannot lock (some network and FUSE mounts) | Explain it and let the person decide. The fix is a local filesystem for XDG_CONFIG_HOME, and moving it hides every profile and stored file credential. After such a move do not reconnect the agent: that rotates its secret. |
A setup the person stops also writes nothing.
Failed readiness checks
Every run checks every served project, including the ones it keeps. A kept project that fails blocks the whole write, so fix it or stop serving it before other changes can land.
- Projects: No project is served. A failure only on a first setup, where it means the profile has not been set up: serve a project (step 4). On a profile already set up it is a warning, not a failure — connect.json is written, and the agent is left doing nothing until a project is served again.
- Project
<id>: reading the project was refused, and the message says Basecamp refuses this read to an Agent identity today. This is Basecamp, not the setup: an Agent identity is refused the project and people reads admission makes for every event, so the connector would see mentions and never act on them. First check the agent is a member of the project. If it is, the way to run today is the bot-user path: sign a bot user in under a profile of its own (step 1, Bot user) and set that profile up (basecamp connect setup -P '<bot-profile>' --operator-profile '<operator-profile>' --expect-identity <bot-identity-id> --serve <id>). The Agent profile's credential stays as it is. Explain this and let the person decide before starting a bot-user sign-in: it needs a bot user account and its identity id. - Project
<id>: refused, without the Agent message. The agent (a bot user) cannot see the project. Add it to the project in Basecamp and run setup again. - Stream ticket: Basecamp refused the ticket mint. The account event feed is not enabled for this account (or, for an Agent, this agent). That is a Basecamp-side setting; the person has to ask for it to be enabled.
- Scope: not full access. The agent could not reply. For an Agent profile,
connect again with full access (
basecamp auth agent connect -P '<profile>', approving full access), with the person's consent since it rotates the secret. For a bot user, sign in again with full access and the same pin (basecamp auth login -P '<bot-profile>' --expect-identity <bot-identity-id>). Never switch a bot-user profile to an Agent connection.
Shortened here. Read the whole file on GitHub.
Signals
- GitHub stars
- 278
- Forks
- 21
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
basecamp-connect- Source
- github.com/basecamp/basecamp-cli