> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stackone.com/llms.txt
> Use this file to discover all available pages before exploring further.

# GitHub CI/CD

> Validate and publish connectors automatically with GitHub Actions.

Publishing from a Git pipeline instead of your own machine means every change is validated on the pull request, and merging is what pushes the connector to a project. It also gives you one API key per environment rather than one on each developer's laptop.

<Frame>
  <img src="https://mintcdn.com/stackone-60/EwDvhPIppDul_dNC/images/guides/cicd-workflow.svg?fit=max&auto=format&n=EwDvhPIppDul_dNC&q=85&s=70177ad51638fa521448cf4f053f27ab" alt="CI/CD workflow: Edit YAML → Validate → Push → GitHub Actions → StackOne Projects (dev/staging/prod)" width="720" height="140" data-path="images/guides/cicd-workflow.svg" />
</Frame>

The pipeline is yours to shape:

* **Single project**: push to one project from any branch
* **Multi-environment**: route branches to separate projects, such as develop to dev and main to production
* **Custom**: your own branch strategy and approval gates

## Repository structure

Organize the repository however suits your team. What matters is that the connector directories keep the layout described in [File Structure](/connector-yaml-reference/file-structure), so `validate` and `push` can be pointed at a single parent directory:

```
custom-connectors/
├── .github/
│   └── workflows/
│       └── deploy.yml              # CI/CD pipeline
├── connectors/
│   └── {provider}/
│       ├── {provider}.connector.s1.yaml
│       └── {provider}.{resource}.s1.partial.yaml
└── README.md
```

## Add your API key as a secret

In the repository, go to **Settings → Secrets and variables → Actions** and add the API key for the project you're publishing to:

| Secret             | Description                       |
| ------------------ | --------------------------------- |
| `STACKONE_API_KEY` | API key for your StackOne project |

The key needs the `connectors:write` scope to push. See [API Keys](/embed/api-keys) for generating one.

## Create the workflow file

Create `.github/workflows/deploy.yml`. Both examples validate on pull requests and publish only on a push to a release branch:

<Tabs>
  <Tab title="Single project">
    ```yaml theme={null}
    name: Validate and Deploy Connectors

    on:
      push:
        branches: [main]
        paths:
          - 'connectors/**'
      pull_request:
        branches: [main]
        paths:
          - 'connectors/**'

    jobs:
      validate-and-deploy:
        name: Validate and Deploy
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4

          - name: Setup Node.js
            uses: actions/setup-node@v4
            with:
              node-version: '20'

          - name: Install StackOne CLI
            run: npm install -g @stackone/cli

          - name: Validate connectors
            run: stackone validate connectors/

          - name: Check agent skills are in sync
            run: stackone agent sync --check

          - name: Deploy
            if: github.event_name == 'push'
            env:
              STACKONE_API_KEY: ${{ secrets.STACKONE_API_KEY }}
            run: stackone push connectors/ --api-key $STACKONE_API_KEY
    ```
  </Tab>

  <Tab title="Multi-environment">
    For separate dev, staging, and production projects, add one secret per project:

    | Secret                     | Description         |
    | -------------------------- | ------------------- |
    | `STACKONE_API_KEY_DEV`     | Development project |
    | `STACKONE_API_KEY_STAGING` | Staging project     |
    | `STACKONE_API_KEY_PROD`    | Production project  |

    ```yaml theme={null}
    name: Validate and Deploy Connectors

    on:
      push:
        branches: [develop, staging, main]
        paths:
          - 'connectors/**'
      pull_request:
        branches: [develop, staging, main]
        paths:
          - 'connectors/**'

    jobs:
      validate:
        name: Validate Connectors
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - uses: actions/setup-node@v4
            with:
              node-version: '20'
          - run: npm install -g @stackone/cli
          - run: stackone validate connectors/
          - run: stackone agent sync --check

      deploy-dev:
        needs: validate
        if: github.ref == 'refs/heads/develop' && github.event_name == 'push'
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - uses: actions/setup-node@v4
            with:
              node-version: '20'
          - run: npm install -g @stackone/cli
          - run: stackone push connectors/ --api-key ${{ secrets.STACKONE_API_KEY_DEV }}

      deploy-staging:
        needs: validate
        if: github.ref == 'refs/heads/staging' && github.event_name == 'push'
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - uses: actions/setup-node@v4
            with:
              node-version: '20'
          - run: npm install -g @stackone/cli
          - run: stackone push connectors/ --api-key ${{ secrets.STACKONE_API_KEY_STAGING }}

      deploy-prod:
        needs: validate
        if: github.ref == 'refs/heads/main' && github.event_name == 'push'
        runs-on: ubuntu-latest
        environment: production  # Optional: require manual approval
        steps:
          - uses: actions/checkout@v4
          - uses: actions/setup-node@v4
            with:
              node-version: '20'
          - run: npm install -g @stackone/cli
          - run: stackone push connectors/ --api-key ${{ secrets.STACKONE_API_KEY_PROD }}
    ```
  </Tab>
</Tabs>

`stackone agent sync --check` compares the `.claude/skills/` and `CLAUDE.md` guide committed in the repository against the ones bundled with the installed CLI, and exits non-zero if they differ. It needs no credentials.

<Warning>
  Because the workflow installs the latest CLI, a new CLI release that ships updated skills will fail this step until someone runs `stackone agent sync` and commits the result. Pin the CLI version in the workflow if you'd rather choose when that happens.
</Warning>

## Test after deploying

A published connector needs a linked account before you can call anything against it:

<Steps>
  <Step title="Enable the connector">
    Turn your custom connector on in the project's [connector profiles](https://app.stackone.com/connector_profiles), and enable the actions you want exposed.

    <Card title="Managing Connectors" icon="gear" href="/connect/managing-connectors/overview">
      Connector profiles, authentication, and scoping.
    </Card>
  </Step>

  <Step title="Link an account">
    Create a linked account through the [Hub](/embed/account-linking/overview) or the API.
  </Step>

  <Step title="Run it">
    Select the account in the [Playground](/embed/call-actions/troubleshooting/playground) and try the actions in natural language, or run one directly:

    ```bash theme={null}
    stackone run \
      --connector connectors/[provider]/[provider].connector.s1.yaml \
      --account-id [account-id] \
      --profile [profile-label] \
      --action-id [action-id]
    ```

    <Note>
      The profile's API key needs the `credentials:read` scope to use a linked account's stored credentials.
    </Note>
  </Step>

  <Step title="Iterate">
    Push updates as you refine the connector. Existing linked accounts are unaffected until you move them, which is covered in [Connector Versioning](/connector-building/connector-versioning).
  </Step>
</Steps>

## Common errors

<AccordionGroup>
  <Accordion title="Validation fails in CI but passes locally">
    * Check YAML indentation, which must be 2 spaces rather than tabs
    * Partial files start with `-`, not `actions:`
    * Every `$ref` matches an actual file name, which matters on case-sensitive CI runners even when it works on macOS
    * Run `stackone validate connectors/` locally against the same parent directory the workflow uses
  </Accordion>

  <Accordion title="Push fails with an authentication error">
    * The secret name in the workflow matches the one in repository settings
    * The API key carries the `connectors:write` scope
    * The key belongs to the project you intended to publish to, not another environment
  </Accordion>

  <Accordion title="Workflow does not trigger">
    * The file sits in `.github/workflows/`
    * Branch names in `on.push.branches` match your actual branches
    * The `paths` filter covers the files you changed. Editing only a workflow or README will not match `connectors/**`
  </Accordion>
</AccordionGroup>

## Next steps

<CardGroup cols={3}>
  <Card title="Build Workflow" icon="diagram-project" href="/connector-building/build-workflow">
    The build and debug loop that comes before publishing.
  </Card>

  <Card title="Connector Versioning" icon="code-branch" href="/connector-building/connector-versioning">
    Release new versions without breaking existing linked accounts.
  </Card>

  <Card title="StackOne CLI" icon="terminal" href="/connector-building/stackone-cli">
    Full reference for validate, run, and push.
  </Card>
</CardGroup>
