diff --git a/__snapshots__/runner.js b/__snapshots__/runner.js
new file mode 100644
index 000000000..2aee43fc2
--- /dev/null
+++ b/__snapshots__/runner.js
@@ -0,0 +1,18 @@
+exports['Runner release-pr allows customization of changelog sections 1'] = `
+[
+  [
+    "CHANGELOG.md",
+    {
+      "content": "# Changelog\\n\\n### [1.0.1](https://www.github.com/googleapis/release-please/compare/v1.0.0...v1.0.1) (1983-10-10)\\n\\n\\n### Other\\n\\n* **deps:** update dependency com.google.cloud:google-cloud-spanner to v1.50.0 ([1f9663c](https://www.github.com/googleapis/release-please/commit/1f9663cf08ab1cf3b68d95dee4dc99b7c4aac373))\\n* **deps:** update dependency com.google.cloud:google-cloud-storage to v1.120.0 ([fcd1c89](https://www.github.com/googleapis/release-please/commit/fcd1c890dc1526f4d62ceedad561f498195c8939))\\n* update common templates ([3006009](https://www.github.com/googleapis/release-please/commit/3006009a2b1b2cb4bd5108c0f469c410759f3a6a))\\n",
+      "mode": "100644"
+    }
+  ],
+  [
+    "package.json",
+    {
+      "content": "{\\n  \\"name\\": \\"runner-package\\",\\n  \\"version\\": \\"1.0.1\\"\\n}\\n",
+      "mode": "100644"
+    }
+  ]
+]
+`
diff --git a/src/bin/command.ts b/src/bin/command.ts
new file mode 100644
index 000000000..f8af374f1
--- /dev/null
+++ b/src/bin/command.ts
@@ -0,0 +1,200 @@
+#!/usr/bin/env node
+
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import chalk = require('chalk');
+import {coerceOption} from '../util/coerce-option';
+import {GitHubReleaseOptions} from '../github-release';
+import {ReleasePROptions} from '../release-pr';
+import {getReleaserNames} from '../releasers';
+import main from '../runner';
+import * as yargs from 'yargs';
+
+export interface ErrorObject {
+  body?: object;
+  status?: number;
+  message: string;
+  stack: string;
+}
+
+interface YargsOptions {
+  describe: string;
+  choices?: string[];
+  demand?: boolean;
+  type?: string;
+  default?: string | boolean;
+}
+
+interface YargsOptionsBuilder {
+  option(opt: string, options: YargsOptions): YargsOptionsBuilder;
+}
+
+export default yargs
+  .config('config')
+  .command(
+    'release-pr',
+    'create or update a PR representing the next release',
+    (yargs: YargsOptionsBuilder) => {
+      yargs
+        .option('package-name', {
+          describe: 'name of package release is being minted for',
+          demand: true,
+        })
+        .option('changelog-types', {
+          describe:
+            'a JSON formatted string containing to override the outputted changelog sections',
+        })
+        .option('version-file', {
+          describe: 'path to version file to update, e.g., version.rb',
+        })
+        .option('last-package-version', {
+          describe: 'last version # that package was released as',
+        })
+        .option('repo-url', {
+          describe: 'GitHub URL to generate release for',
+          demand: true,
+        })
+        .option('fork', {
+          describe: 'should the PR be created from a fork',
+          type: 'boolean',
+          default: false,
+        })
+        .option('label', {
+          describe: 'label(s) to add to generated PR',
+        })
+        .option('snapshot', {
+          describe: 'is it a snapshot (or pre-release) being generated?',
+          type: 'boolean',
+          default: false,
+        })
+        .option('default-branch', {
+          describe: 'default branch to open release PR against',
+          type: 'string',
+        })
+        .option('path', {
+          describe: 'release from path other than root directory',
+          type: 'string',
+        })
+        .option('monorepo-tags', {
+          describe: 'include library name in tags and release branches',
+          type: 'boolean',
+          default: false,
+        });
+    },
+    async (argv: ReleasePROptions & yargs.Arguments) => {
+      await main(argv, 'release-pr').catch(e => handleError(e, argv));
+    }
+  )
+  .command(
+    'github-release',
+    'create a GitHub release from a release PR',
+    (yargs: YargsOptionsBuilder) => {
+      yargs
+        .option('package-name', {
+          describe: 'name of package release is being minted for',
+        })
+        .option('repo-url', {
+          describe: 'GitHub URL to generate release for',
+          demand: true,
+        })
+        .option('changelog-path', {
+          default: 'CHANGELOG.md',
+          describe: 'where can the CHANGELOG be found in the project?',
+        })
+        .option('label', {
+          default: 'autorelease: pending',
+          describe: 'label to remove from release PR',
+        })
+        .option('release-type', {
+          describe: 'what type of repo is a release being created for?',
+          choices: getReleaserNames(),
+          default: 'node',
+        })
+        .option('path', {
+          describe: 'release from path other than root directory',
+          type: 'string',
+        });
+    },
+    async (argv: GitHubReleaseOptions & yargs.Arguments) => {
+      await main(argv, 'github-release').catch(e => handleError(e, argv));
+    }
+  )
+  .middleware(_argv => {
+    const argv = _argv as GitHubReleaseOptions;
+    // allow secrets to be loaded from file path
+    // rather than being passed directly to the bin.
+    if (argv.token) argv.token = coerceOption(argv.token);
+    if (argv.apiUrl) argv.apiUrl = coerceOption(argv.apiUrl);
+    if (argv.proxyKey) argv.proxyKey = coerceOption(argv.proxyKey);
+  })
+  .option('token', {describe: 'GitHub token with repo write permissions'})
+  .option('release-as', {
+    describe: 'override the semantically determined release version',
+    type: 'string',
+  })
+  .option('release-type', {
+    describe: 'what type of repo is a release being created for?',
+    choices: getReleaserNames(),
+    default: 'node',
+  })
+  .option('bump-minor-pre-major', {
+    describe:
+      'should we bump the semver minor prior to the first major release',
+    default: false,
+    type: 'boolean',
+  })
+  .option('api-url', {
+    describe: 'URL to use when making API requests',
+    default: 'https://api.github.com',
+    type: 'string',
+  })
+  .option('proxy-key', {
+    describe: 'key used by some GitHub proxies',
+    type: 'string',
+  })
+  .option('debug', {
+    describe: 'print verbose errors (use only for local debugging).',
+    default: false,
+    type: 'boolean',
+  })
+  .option('default-branch', {
+    describe: '',
+    type: 'string',
+  })
+  .demandCommand(1)
+  .strict(true);
+
+// The errors returned by octokit currently contain the
+// request object, this contains information we don't want to
+// leak. For this reason, we capture exceptions and print
+// a less verbose error message (run with --debug to output
+// the request object, don't do this in CI/CD).
+export function handleError(err: ErrorObject, argv: yargs.Arguments) {
+  let status = '';
+  const command = argv._.length === 0 ? '' : argv._[0];
+  if (err.status) {
+    status = '' + err.status;
+  }
+  console.error(
+    chalk.red(
+      `command ${command} failed${status ? ` with status ${status}` : ''}`
+    )
+  );
+  if (argv.debug) {
+    console.error('---------');
+    console.error(err.stack);
+  }
+  process.exitCode = 1;
+}
diff --git a/src/bin/release-please.ts b/src/bin/release-please.ts
index b6dd5655b..87400f355 100644
--- a/src/bin/release-please.ts
+++ b/src/bin/release-please.ts
@@ -1,5 +1,3 @@
-#!/usr/bin/env node
-
 // Copyright 2019 Google LLC
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,199 +11,14 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
+import command, {ErrorObject, handleError} from './command';
 
-import chalk = require('chalk');
-import {coerceOption} from '../util/coerce-option';
-import {GitHubRelease, GitHubReleaseOptions} from '../github-release';
-import {ReleasePROptions} from '../release-pr';
-import {ReleasePRFactory} from '../release-pr-factory';
-import {getReleaserNames} from '../releasers';
-import * as yargs from 'yargs';
-
-interface ErrorObject {
-  body?: object;
-  status?: number;
-  message: string;
-  stack: string;
-}
-
-interface YargsOptions {
-  describe: string;
-  choices?: string[];
-  demand?: boolean;
-  type?: string;
-  default?: string | boolean;
-}
-
-interface YargsOptionsBuilder {
-  option(opt: string, options: YargsOptions): YargsOptionsBuilder;
-}
-
-const argv = yargs
-  .command(
-    'release-pr',
-    'create or update a PR representing the next release',
-    (yargs: YargsOptionsBuilder) => {
-      yargs
-        .option('package-name', {
-          describe: 'name of package release is being minted for',
-          demand: true,
-        })
-        .option('version-file', {
-          describe: 'path to version file to update, e.g., version.rb',
-        })
-        .option('last-package-version', {
-          describe: 'last version # that package was released as',
-        })
-        .option('repo-url', {
-          describe: 'GitHub URL to generate release for',
-          demand: true,
-        })
-        .option('fork', {
-          describe: 'should the PR be created from a fork',
-          type: 'boolean',
-          default: false,
-        })
-        .option('label', {
-          describe: 'label(s) to add to generated PR',
-        })
-        .option('snapshot', {
-          describe: 'is it a snapshot (or pre-release) being generated?',
-          type: 'boolean',
-          default: false,
-        })
-        .option('default-branch', {
-          describe: 'default branch to open release PR against',
-          type: 'string',
-        })
-        .option('path', {
-          describe: 'release from path other than root directory',
-          type: 'string',
-        })
-        .option('monorepo-tags', {
-          describe: 'include library name in tags and release branches',
-          type: 'boolean',
-          default: false,
-        });
-    },
-    (argv: ReleasePROptions) => {
-      const rp = ReleasePRFactory.build(argv.releaseType, argv);
-      rp.run().catch(handleError);
-    }
-  )
-  .command(
-    'github-release',
-    'create a GitHub release from a release PR',
-    (yargs: YargsOptionsBuilder) => {
-      yargs
-        .option('package-name', {
-          describe: 'name of package release is being minted for',
-        })
-        .option('repo-url', {
-          describe: 'GitHub URL to generate release for',
-          demand: true,
-        })
-        .option('changelog-path', {
-          default: 'CHANGELOG.md',
-          describe: 'where can the CHANGELOG be found in the project?',
-        })
-        .option('label', {
-          default: 'autorelease: pending',
-          describe: 'label to remove from release PR',
-        })
-        .option('release-type', {
-          describe: 'what type of repo is a release being created for?',
-          choices: getReleaserNames(),
-          default: 'node',
-        })
-        .option('path', {
-          describe: 'release from path other than root directory',
-          type: 'string',
-        })
-        .option('monorepo-tags', {
-          describe: 'include library name in tags and release branches',
-          type: 'boolean',
-          default: false,
-        });
-    },
-    (argv: GitHubReleaseOptions) => {
-      const gr = new GitHubRelease(argv);
-      gr.createRelease().catch(handleError);
-    }
-  )
-  .middleware(_argv => {
-    const argv = _argv as GitHubReleaseOptions;
-    // allow secrets to be loaded from file path
-    // rather than being passed directly to the bin.
-    if (argv.token) argv.token = coerceOption(argv.token);
-    if (argv.apiUrl) argv.apiUrl = coerceOption(argv.apiUrl);
-    if (argv.proxyKey) argv.proxyKey = coerceOption(argv.proxyKey);
-  })
-  .option('token', {describe: 'GitHub token with repo write permissions'})
-  .option('release-as', {
-    describe: 'override the semantically determined release version',
-    type: 'string',
-  })
-  .option('release-type', {
-    describe: 'what type of repo is a release being created for?',
-    choices: getReleaserNames(),
-    default: 'node',
-  })
-  .option('bump-minor-pre-major', {
-    describe:
-      'should we bump the semver minor prior to the first major release',
-    default: false,
-    type: 'boolean',
-  })
-  .option('api-url', {
-    describe: 'URL to use when making API requests',
-    default: 'https://api.github.com',
-    type: 'string',
-  })
-  .option('proxy-key', {
-    describe: 'key used by some GitHub proxies',
-    type: 'string',
-  })
-  .option('debug', {
-    describe: 'print verbose errors (use only for local debugging).',
-    default: false,
-    type: 'boolean',
-  })
-  .option('default-branch', {
-    describe: '',
-    type: 'string',
-  })
-  .demandCommand(1)
-  .strict(true)
-  .parse();
-
-// The errors returned by octokit currently contain the
-// request object, this contains information we don't want to
-// leak. For this reason, we capture exceptions and print
-// a less verbose error message (run with --debug to output
-// the request object, don't do this in CI/CD).
-function handleError(err: ErrorObject) {
-  let status = '';
-  const command = argv._.length === 0 ? '' : argv._[0];
-  if (err.status) {
-    status = '' + err.status;
-  }
-  console.error(
-    chalk.red(
-      `command ${command} failed${status ? ` with status ${status}` : ''}`
-    )
-  );
-  if (argv.debug) {
-    console.error('---------');
-    console.error(err.stack);
-  }
-  process.exitCode = 1;
-}
+const argv = command.parse();
 
 process.on('unhandledRejection', err => {
-  handleError(err as ErrorObject);
+  handleError(err as ErrorObject, argv);
 });
 
 process.on('uncaughtException', err => {
-  handleError(err as ErrorObject);
+  handleError(err as ErrorObject, argv);
 });
diff --git a/src/release-pr.ts b/src/release-pr.ts
index 5a1efa46f..f88a0b7b7 100644
--- a/src/release-pr.ts
+++ b/src/release-pr.ts
@@ -52,9 +52,15 @@ export interface BuildOptions {
   versionFile?: string;
 }
 
+type ChangelogSection = {
+  type: string;
+  section: string;
+  hidden?: boolean;
+};
+
 export interface ReleasePROptions extends BuildOptions {
   releaseType: string;
-  changelogSections?: [];
+  changelogSections?: Array<ChangelogSection>;
 }
 
 export interface ReleaseCandidate {
@@ -97,7 +103,7 @@ export class ReleasePR {
   proxyKey?: string;
   snapshot?: boolean;
   lastPackageVersion?: string;
-  changelogSections?: [];
+  changelogSections?: Array<ChangelogSection>;
 
   constructor(options: ReleasePROptions) {
     this.bumpMinorPreMajor = options.bumpMinorPreMajor || false;
diff --git a/src/runner.ts b/src/runner.ts
new file mode 100644
index 000000000..64432da20
--- /dev/null
+++ b/src/runner.ts
@@ -0,0 +1,47 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import {GitHubRelease, GitHubReleaseOptions} from './github-release';
+import {ReleasePRFactory} from './release-pr-factory';
+import {ReleasePROptions} from './release-pr';
+
+type Commands = 'github-release' | 'release-pr';
+
+interface MappedOptions {
+  changelogTypes?: string;
+}
+
+type Options = (GitHubReleaseOptions | ReleasePROptions) & MappedOptions;
+
+export default async function main(
+  options: Options,
+  command?: Commands,
+  onReleaseCreated?: Function
+) {
+  if (!command || command === 'github-release') {
+    const gr = new GitHubRelease(options as GitHubReleaseOptions);
+    const releaseCreated = await gr.createRelease();
+    if (releaseCreated && onReleaseCreated) {
+      onReleaseCreated(releaseCreated);
+    }
+  }
+  if (!command || command === 'release-pr') {
+    const opts = options as ReleasePROptions;
+    if (options.changelogTypes) {
+      opts.changelogSections = JSON.parse(options.changelogTypes);
+    }
+    const release = ReleasePRFactory.buildStatic(opts.releaseType, opts);
+    await release.run();
+  }
+}
diff --git a/test/cli.ts b/test/cli.ts
new file mode 100644
index 000000000..9031ea11d
--- /dev/null
+++ b/test/cli.ts
@@ -0,0 +1,67 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import {resolve} from 'path';
+
+import {describe, it} from 'mocha';
+import {expect, assert} from 'chai';
+
+import command from '../src/bin/command';
+
+function getExampleConfigurationPath() {
+  return resolve('./test/fixtures/config', 'simple.json');
+}
+
+describe('CLI', () => {
+  describe('release-pr', () => {
+    it('can be configured using flags', () => {
+      const argv = command.parse(
+        'release-pr --repo-url=googleapis/release-please-cli --package-name=cli-package'
+      );
+      expect(argv).includes({
+        repoUrl: 'googleapis/release-please-cli',
+        releaseType: 'node',
+        packageName: 'cli-package',
+      });
+    });
+
+    it('can be configured using a file', () => {
+      const argv = command.parse(
+        `release-pr --config=${getExampleConfigurationPath()}`
+      );
+      expect(argv).includes({
+        repoUrl: 'googleapis/release-please-cli',
+        releaseType: 'node',
+        packageName: 'cli-package--config',
+      });
+      expect(argv.changelogTypes).to.be.a('string').that.is.not.empty;
+    });
+
+    it('converts changelog-types => changelogSections', () => {
+      const argv = command.parse(
+        `release-pr --config=${getExampleConfigurationPath()}`
+      );
+      expect(argv).includes({
+        repoUrl: 'googleapis/release-please-cli',
+        releaseType: 'node',
+        packageName: 'cli-package--config',
+      });
+      expect(argv.changelogTypes).to.be.a('string').that.is.not.empty;
+      assert.sameDeepMembers(
+        JSON.parse(argv.changelogTypes as string),
+        argv.changelogSections as []
+      );
+    });
+  });
+});
diff --git a/test/fixtures/config/simple.json b/test/fixtures/config/simple.json
new file mode 100644
index 000000000..ea3fcae20
--- /dev/null
+++ b/test/fixtures/config/simple.json
@@ -0,0 +1,5 @@
+{
+  "package-name": "cli-package--config",
+  "repo-url": "googleapis/release-please-cli",
+  "changelog-types": "[{\"type\":\"feat\",\"section\":\"Features\"},{\"type\":\"fix\",\"section\":\"Bug Fixes\"},{\"type\":\"perf\",\"section\":\"Performance Improvements\"},{\"type\":\"deps\",\"section\":\"Dependencies\"},{\"type\":\"revert\",\"section\":\"Reverts\"},{\"type\":\"docs\",\"section\":\"Documentation\"},{\"type\":\"style\",\"section\":\"Styles\",\"hidden\":true},{\"type\":\"chore\",\"section\":\"Miscellaneous Chores\"},{\"type\":\"refactor\",\"section\":\"Code Refactoring\",\"hidden\":true},{\"type\":\"test\",\"section\":\"Tests\",\"hidden\":true},{\"type\":\"build\",\"section\":\"Build System\",\"hidden\":true},{\"type\":\"ci\",\"section\":\"Continuous Integration\",\"hidden\":true}]"
+}
\ No newline at end of file
diff --git a/test/runner.ts b/test/runner.ts
new file mode 100644
index 000000000..5a47ac2dd
--- /dev/null
+++ b/test/runner.ts
@@ -0,0 +1,148 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import {resolve} from 'path';
+import {readFileSync} from 'fs';
+
+import {describe, it, afterEach} from 'mocha';
+import * as suggester from 'code-suggester';
+import * as sinon from 'sinon';
+import * as nock from 'nock';
+import * as snapshot from 'snap-shot-it';
+
+import runner from '../src/runner';
+
+const sandbox = sinon.createSandbox();
+
+describe('Runner', () => {
+  afterEach(() => {
+    sandbox.restore();
+  });
+
+  describe('release-pr', () => {
+    it('allows customization of changelog sections', async () => {
+      // Fake the createPullRequest step, and capture a set of files to assert against:
+      let expectedChanges = null;
+      sandbox.replace(
+        suggester,
+        'createPullRequest',
+        (_octokit, changes): Promise<number> => {
+          expectedChanges = [...(changes as Map<string, object>)]; // Convert map to key/value pairs.
+          return Promise.resolve(22);
+        }
+      );
+
+      const graphql = JSON.parse(
+        readFileSync(
+          resolve('./test/releasers/fixtures/node', 'commits.json'),
+          'utf8'
+        )
+      );
+
+      const existingPackageResponse = {
+        content: Buffer.from(
+          JSON.stringify({
+            name: 'runner-package',
+            version: '1.0.0',
+          }),
+          'utf8'
+        ).toString('base64'),
+        sha: 'abc123',
+      };
+
+      const scope = nock('https://api.github.com')
+        // Check for in progress, merged release PRs:
+        .get(
+          '/repos/googleapis/release-please/pulls?state=closed&per_page=100&sort=merged_at&direction=desc'
+        )
+        .reply(200, undefined)
+        // fetch semver tags, this will be used to determine
+        // the delta since the last release.
+        .get('/repos/googleapis/release-please/tags?per_page=100')
+        .reply(200, [
+          {
+            name: 'v1.0.0',
+            commit: {
+              sha: 'da6e52d956c1e35d19e75e0f2fdba439739ba364',
+            },
+          },
+        ])
+        .get(
+          '/repos/googleapis/release-please/pulls?state=closed&per_page=100&sort=merged_at&direction=desc'
+        )
+        .reply(200, undefined)
+        // now we fetch the commits via the graphql API;
+        // note they will be truncated to just before the tag's sha.
+        .post('/graphql')
+        .reply(200, {
+          data: graphql,
+        })
+        .get(
+          '/repos/googleapis/release-please/contents/package.json?ref=refs%2Fheads%2Fmaster'
+        )
+        .reply(200, existingPackageResponse)
+        .get('/repos/googleapis/release-please')
+        // eslint-disable-next-line @typescript-eslint/no-var-requires
+        .reply(200, require('../../test/fixtures/repo-get-1.json'))
+        .get(
+          '/repos/googleapis/release-please/contents/CHANGELOG.md?ref=refs%2Fheads%2Fmaster'
+        )
+        .reply(404)
+        .get(
+          '/repos/googleapis/release-please/contents/package-lock.json?ref=refs%2Fheads%2Fmaster'
+        )
+        .reply(404)
+        .get(
+          '/repos/googleapis/release-please/contents/samples%2Fpackage.json?ref=refs%2Fheads%2Fmaster'
+        )
+        .reply(404)
+        .get(
+          '/repos/googleapis/release-please/contents/package.json?ref=refs%2Fheads%2Fmaster'
+        )
+        .reply(200, existingPackageResponse)
+        // this step tries to match any existing PRs; just return an empty list.
+        .get('/repos/googleapis/release-please/pulls?state=open&per_page=100')
+        .reply(200, [])
+        // Add autorelease: pending label to release PR:
+        .post('/repos/googleapis/release-please/issues/22/labels')
+        .reply(200)
+        // this step tries to close any existing PRs; just return an empty list.
+        .get('/repos/googleapis/release-please/pulls?state=open&per_page=100')
+        .reply(200, []);
+
+      await runner(
+        {
+          apiUrl: 'https://api.github.com',
+          repoUrl: 'googleapis/release-please',
+          releaseType: 'node',
+          packageName: 'runner-package',
+          changelogSections: [
+            {type: 'feat', section: 'Features'},
+            {type: 'fix', section: 'Other'},
+            {type: 'chore', section: 'Other'},
+          ],
+        },
+        'release-pr'
+      );
+
+      scope.done();
+      snapshot(
+        JSON.stringify(expectedChanges, null, 2).replace(
+          /[0-9]{4}-[0-9]{2}-[0-9]{2}/,
+          '1983-10-10' // don't save a real date, this will break tests.
+        )
+      );
+    });
+  });
+});