Skip to content

DocOps/issuer

Repository files navigation

Issuer: Bulk GitHub Issue Creator

Overview

Issuer lets you define all your GitHub Issues work tickets in one place as YAML, apply defaults, and post them to GitHub Issues in bulk.

Features

  • Bulk issue creation from a single YAML file

  • Dry-run mode for testing without creating issues

  • Automatic milestone creation if milestones don’t exist

  • Configurable defaults and label application

  • Environment variable support for authentication

  • Issue validation with helpful error messages

  • GitHub API integration

Future plans include extending this capability to Jira (#9), GitLab Issues (#33), GitHub Projects, and other services.

Who Is This For?

This tool is oriented as much for project managers as it is for software developers, but the target audience is probably people who manage software development cycles, especially using Git tools and GitHub Issues (or soon Jira Issues and GitLab Issues).

📎
Issuer does not require Git be installed on your local workstation. The only direct use of Git would be if you commit and share the batch-configuration file with your team for collective editing.

This tool is great at creating “stub” issues for entire teams, based on user-story documents, task lists, or other planning documents. Once you convert such documents to our special YAML-based definition format (IMYML), use issuer to post them to your GitHub Issues system via GitHub’s API.

Individual issue entries can be as basic or as detailed as you wish. They may consist of as little as just a title (summary) line, or that plus an assignee responsible for filling out the rest and adding metadata. Or they can be complete with labels, milestones, a type designation, and a complete explanation of the issue.

Installation

For end users, there are two ways to execute issuer. If you do not already operate in a Ruby environment, the Docker approach is recommended.

For Non-Ruby Users

If you are not already a Ruby user, the issuer utility may be best used from our Docker image.

📎
You will need Docker Desktop installed directly on MacOS or with WSL2 backend on Windows, but all instructions here work in the terminal. For Linux, use the Docker Engine install docs if you’re not already using Docker.

With Docker installed and running…​

  1. Download the Docker image.

    docker pull docopslab/issuer
  2. Run the issuer command via a container based on the Docker image.

    docker run -it --rm -v $(pwd):/workdir -e ISSUER_API_TOKEN=$GITHUB_TOKEN docopslab/issuer --version

For actual issue creation, use something like:

docker run -it --rm -v $(pwd):/workdir -e ISSUER_API_TOKEN=$GITHUB_TOKEN docopslab/issuer your-issues.yml

The above command:

  • mounts your local directory to be readable by the Docker container

  • passes your GitHub access token environment variable into the container ($ISSUER_API_TOKEN could be $ISSUER_GITHUB_TOKEN, $GITHUB_ACCESS_TOKEN, or $GITHUB_TOKEN).

  • connects to GitHub and creates issue entries one at a time from your-issues.yml

Everything after docopslab/issuer accepts the standard arguments and options of the issuer CLI.

See Usage for more.

Alias the Docker Command

Optionally alias the base Docker command. In your shell configuration (usually ~/.bashrc or `~/.zshrc), add the following:

alias issuer='docker run -it --rm -v $(pwd):/workdir -e ISSUER_API_TOKEN=$GITHUB_TOKEN docopslab/issuer'

Reload your shell configuration for the alias to take effect:

source ~/.bashrc

For Ruby Users

If you have Ruby on your workstation, there are two common ways to install this gem.

Global Installation

gem install issuer

Then you can use the issuer command from anywhere in your system.

Local/Application Installation

Add this line to your Gemfile:

gem 'issuer'

And then execute:

bundle install

Now you can use bundle exec issuer to perform operations inside the project directory.

Usage

Once installed, you can start using issuer to create issues in GitHub.

Quickstart Overview

The following steps assume the gem is either installed globally or issuer is established as an alias (Docker method). For Ruby Bundler usage, prepend bundle exec ` and for un-aliased Docker usage, prepend `docker run -it --rm -v $(pwd):/workdir -e ISSUER_API_TOKEN=$GITHUB_TOKEN docopslab/issuer.

  1. Prepare your issue definitions in an IMYML file (see examples and docs below).

  2. Perform a “dry run” to validate your file and check what would be posted (no GitHub API calls made):

    issuer example.yml --dry
  3. Establish a Personal Access Token for GitHub and store it as an environment variable (see Authentication below).

  4. Post issues to GitHub:

    issuer example.yml

IMYML File Format

The specially formatted configuration files are structured as IMYML, for Issue Management YAML-based Modeling Format.

The YAML file must have the following structure:

$meta: # optional block for establishing general modes/settings
  proj: org/repo
  defaults: # value to infer when given property missing
    vrsn: 0.1.0 # milestone/version
    user: alice # assigned user
    type: Bug # type of issue (must already be registered)
    tags: [needs:labels, +posted_by_issuer, +needs:docs] # labels
    stub: true # whether to auto-insert stub texts
    head: | # header stub text to prepend when indicated
      Below the next line is the body...
      ---
    tail: | # footer stub text to append when indicated
      ---
      This issue was automatically generated by issuer.
    body: | # body text to impose when no body provided
      This is the default text that will appear if an issue record is a _stub_ and no `body` field is designated.
issues: # block for listing issues to post to cloud
  - summ: Issue title # title/summary field
    type: Task # type of issue (must already be registered)
    body: | # description/body field
      Markdown-formatted description.
    tags: [label1, 'component:api'] # labels to create/assign
    user: someusername # assigned username
    stub: false
  - summ: Another issue
    vrsn: 0.2.0 # milestone
    # this issue record's body will be:
    # Below the next line is the body...
    # ---
    # This is the default text that will appear if an issue record is a _stub_ and no `body` field is designated.
    # ---
    # This issue was automatically generated by issuer.
  - summ: Documentation issue
    tags: [-needs:docs] # skip the default needs:docs label

The $meta block is entirely optional, but if it is absent, your issuer command will need a --proj flag to designate the GitHub repo to which your issues should post.

Only the summ property is required for each issue record, and issue records (Array items) that are simple strings will be treated as summary-only. Therefore, the following example would yield 3 tickets with unique summaries and the same body, based on $meta.defaults.body.

Issuer will prompt the creation of tags (labels) or versions (milestones) if they do not already exist in the target repository.

Any type entry must correspond to an existing issue type.

$meta:
  defaults:
    body: |
      This is a placeholder text until someone is assigned this ticket.
    stub: true
issues:
  - summ: Fill out issue bodies and add labels/milestones
    body: |
      The rest of the tickets in this project are not filled out.
    tags: ['priority:high']
  - Make a README.adoc file
  - Add a license to the repo
💡
This repository contains numerous example files to use for inspiration.

The IMYML format will be standardized and formally specified in a future release of issuer, but it will remain an open standard adoptable by anyone who wants to exploit or extend it.

IMYML Properties Reference

$meta

Optional block for establishing operation-wide modes and settings.

$meta.proj

(String) Designates the target project/repository.

$meta.defaults

Properties in this block establish the default values to be used for any issue record that does not specify a value for the given property.

$meta.defaults.vrsn

(String) Sets default version or milestone for all issues.

$meta.defaults.user

(String) Sets default assignee (GitHub username).

$meta.defaults.type

(String) Sets default issue type to apply to all issues when no type property is specified in the issue record.

$meta.defaults.tags

(Array): Labels to append to issues (comma-separated). Prepend items with `+` to indicate they should be appended to existing labels. Items without `+` will only be used for issues with no `tags` designated.

$meta.defaults.stub

(Boolean) Establishes the state whether to insert stub texts (body / head / tail).

$meta.defaults.body

(String) Sets default body text to apply to all issues when no body property is specified in the issue record.

$meta.defaults.head

(String) Sets default text to insert before the body of all issues for which stub.

$meta.defaults.tail

(String) Sets default text to insert after the body of all issues for which stub.

issues

(Array) Tabular listing of issue records as Array items. If an item is Scalar (not a Map with named keys), the value must be a String and it will be treated as the summ (summary/title) property.

Otherwise, any issues Array items must be Map-formatted “dictionaries” with the following properties:

summ

(String, required) A one-line title or summary of the issue.

body

(String) The main body or description text for the issue. Defaults to $meta.defaults.body if stub == true for the record, in which case, upon submission, will also incorporate any values for $meta.defaults.head and $meta.defaults.tail.

vrsn

(String) The milestone associated with the issue.

Defaults to $meta.defaults.vrsn or else null.

type

(String) The type of issue, which must already be registered in the target project or repository. Defaults to $meta.defaults.type or else null.

tags

(Array of Strings) A listing of specific labels to assign to the issue.

Supports special prefix notation for label management:

  • Regular labels (example: bug, priority:high) are applied based on default tag logic

  • Append labels (example: +urgent) are always applied to all issues

  • Removal labels (example: -needs:docs) remove the specified label from the default/appended labels list

    Example: tags: [documentation, +critical, -needs:review] would add documentation and critical labels while removing any needs:review label from defaults.

user

(String) The system username of the person or bot to which the ticket is assigned.

stub

(Boolean) Accepts true or false. Whether to treat the issue as a stub entry, meaning prepend any $meta.defaults.head text or append any $meta.defaults.tail text, and in case the ticket has no body property, insert the text of $meta.defaults.body.

CLI Usage

issuer [IMYML_FILE] [options]

IMYML File Path Argument

A source IMYML file is required and can be specified in two ways:

  • Positional argument (most common): Place the file path immediately after issuer

  • Named option: Use the --file option flag to specify the file path

Examples:

issuer my-issues.yml --proj acmeco/widget1 --dry
issuer --proj acmeco/widget1 --file my-issues.yml --dry

File and Meta Override Options

These options specify the source file and override any corresponding $meta.defaults properties.

--file IMYML_FILE

IMYML file path (alternative to positional argument).

--proj ORG/REPO

The target project (org/repo or user/repo format for GitHub).

--vrsn VERSION

Sets default milestone for all issues.

--user USERNAME

Sets default assignee (GitHub username).

--tags TAG[,TAG]

Sets labels to apply to issues (comma-separated). Prepend items with `+` to indicate they should be appended to existing labels. Items without `+` will only be used for issues with no `tags` designated.

--stub [true*|false]

Whether to treat all issues as stubs, meaning prepend any $meta.defaults.head text or append any $meta.defaults.tail text, and in case the ticket has no body property, insert the text of $meta.defaults.body.

Mode Options

--dry, --dry-run

Dry-run: print actions but do not post to GitHub.

--auto-versions, --auto-milestones

Automatically create missing milestones/versions without prompting for confirmation.

--auto-tags, --auto-labels

Automatically create missing labels/tags without prompting for confirmation.

--auto-metadata

Automatically create all missing metadata (milestones and labels) without prompting for confirmation. Equivalent to using both --auto-versions and --auto-tags.

--help, -h

Prints the usage screen.

--version

Prints the version of issuer.

Authentication

GitHub authentication requires a valid personal access token.

The application will check for environment variables in the following order:

  1. ISSUER_API_TOKEN

  2. ISSUER_GITHUB_TOKEN

  3. GITHUB_ACCESS_TOKEN

  4. GITHUB_TOKEN

To create and set a token:

  1. In the GitHub Web interface, go to Settings (under your user icon) → Developer Settings (bottom of left menu) → Personal Access TokensFine-grained tokens.

  2. Generate a new token with access to All repositories or any Select repositories you wish to post to, and include read/write permissions GitHub Issues (under Repository permissions).

  3. Copy the token and set it as an environment variable.

    Example
    export ISSUER_API_TOKEN=github_pat_xxxxxxxxxxxxxxxxxxxxxxxx

    Where github_pat_xxxxxxxxxxxxxxxxxxxxxxxx is your actual token.

If your GitHub token is stored under any other name, you can alias it inline by prepending to your issuer command. For example:

ISSUER_API_TOKEN=$MY_GITHUB_API_KEY issuer my-issues.yml

When using Docker, you can pass any such key into the container this way, using the -e option: -e ISSUER_API_TOKEN=$MY_GITHUB_API_KEY.

Advanced Usage

Logging

Issuer automatically logs all API operations for tracking and potential cleanup.

Log Storage Location

By default, logs are stored in a user-wide directory:

  • Linux/macOS: ~/.config/issuer/logs/

  • With XDG Base Directory: $XDG_CONFIG_HOME/issuer/logs/

  • Custom location: Set ISSUER_CONFIG_DIR environment variable

Example:

# Use custom config directory
export ISSUER_CONFIG_DIR="~/path/to/my/issuer/config"
issuer my-issues.yml

# Logs will be stored in: ~/path/to/my/issuer/config/logs/

Log Management

Use the management script to view and manage your run logs.

For now, you will need to clone the DocOps/issuer repo in order to work with the run-logs manager.

# List all runs
ruby scripts/manage-runs.rb list

# Show details for a specific run
ruby scripts/manage-runs.rb show run_20250711_143022_abcd

# Clean up all logs (use with caution)
ruby scripts/manage-runs.rb clean-logs

Each run creates a detailed log with:

  • All created issues, milestones, and labels

  • URLs for easy access

  • Run metadata and status

  • Error information if the run failed

📎
It is typically safe to delete logs once you are satisfied with your posted issues. Logs are simply kept for easy reversal of mis-postings.

Development

I developed the 0.1.0 version of this application after trying to use GitHub Copilot to automatically bulk-create issue tickets, which it promises to be able to do but failed me pretty hard at it.

That facility seems like a perfectly inappropriate use of generative AI. It accepted my plan request and pre-drafted ticket content, but then it wanted me to manually add labels and milestones to them, as well as manually click create on each one — even though I had already taken the time to plan and instruct the milestones and labels and the contents were fully prepared.

Additionally, I find myself using different issue-management systems (Jira, GitLab Issues, etc), so I wanted a more platform-agnostic way to handle this problem. With that in mind, I have left the Ruby API and the IMYML model fairly “generic” for extensibility. I will probably adapt the API to other systems in future releases, and I welcome contributions to that effect.

Methodology Confession

I should note up front that this is the closest I have come to “vibe coding” anything bigger than a local script, let alone shippable production code. Nevertheless, I intervened to make substantial and specific changes at least 100 times before the 0.1.0 release alone, and I rearranged major aspects of the codebase.

I designed the IMYML format and the CLI up front, then I let Claude 4 (via GH Copilot) draft most of the code. It committed lots of rookie mistakes during this process, and it even confessed to “cargo-cult programming” when I pointed out it was introducing some anti-patterns.

In the end, the only thing that is mainly untouched by me are the rspec tests, which I will more fully examine and approve before any 1.0 release, but for now they’ll have to do.

This also explains why the terminal output contains emojis. I will probably make those togglable or configurable in the future.

Tests

The specs/ directory contains all specifications, requirements, and tests for the Issuer CLI tool.

Directory Structure

specs/
├── tests/
    ├── rspec/
        ├── spec_helper.rb
        ├── cli_spec.rb
        ├── issue_spec.rb
        ├── ops_spec.rb
        └── issuer_spec.rb

Running Tests

From the project root:

# Run all PR tests locally (same as GitHub Actions)
bundle exec rake pr_test

# Run all tests (recommended)
bundle exec rake spec

# Run all tests directly with RSpec
bundle exec rspec

# Run specific test file
bundle exec rspec specs/tests/rspec/cli_spec.rb

# Run tests matching a pattern
bundle exec rspec --pattern "*ops*"
PR Test Suite

The pr_test task runs the exact same tests that GitHub Actions runs for pull requests:

  • RSpec Tests: All unit tests (bundle exec rake spec)

  • CLI Tests: Command-line interface functionality tests

  • YAML Validation: Validates all example YAML files

  • Documentation Quality: Vale linting on all documentation files

This ensures you can validate your changes locally before pushing to GitHub.

Test Organization

RSpec Tests

(specs/tests/rspec/): Executable tests that validate the gem functionality

GitHub API Integration Tests

(specs/tests/github-api/): Comprehensive end-to-end tests for GitHub API integration

Test Data

(specs/tests/data/): YAML files, fixtures, and other test assets

Future

Natural language specifications, API schemas, requirement documents

GitHub API Integration Testing

For comprehensive GitHub API testing, see the GitHub API test suite documentation.

Quick start for API integration testing:

# Check GitHub connectivity
./specs/tests/check-github-connectivity.sh

# Run full GitHub API integration test suite
./specs/tests/run-github-api-tests.sh

# Run with specific options
./specs/tests/run-github-api-tests.sh --verbose --auto-cleanup

The GitHub API test suite validates:

  • Authentication and connectivity

  • Issue creation with various configurations

  • Milestone/version management and automation

  • Label/tag management and automation

  • Assignment functionality

  • Automation flags (--auto-metadata, --auto-versions, etc.)

  • Error handling and edge cases

API Reference

For detailed API documentation, see the automatically generated documentation at GemDocs.

The API reference includes:

  • Complete class and method documentation

  • Method signatures and parameters

  • Return types and examples

  • Internal implementation details

This documentation is automatically updated with each gem release.

Release History Management

As of version 0.2.0, the Release Notes and Changelog are generated using the ReleaseHx tool, which is still in pre-release. You can find the release history assets in the docs/releasehx/ directory, which contains a configuration file and the 0.2.0 “RHYML” file that was auto-modified and then manually edited to produce the GitHub release announcement.

ReleaseHx will be available soon, but for now the following is only usable by DocOps Lab.

  1. Add local releasehx gem to Gemfile:

    gem 'releasehx', path: '../releasehx'
  2. Draft RHYML draft file from the online GitHub Issues for the given version:

    ./releasehx-install.sh && bundle exec rhx 0.2.1 --yaml docs/releasehx/release-0.2.1.yml --config docs/releasehx/config.yml

    The releasehx-install.sh ensures ReleaseHx is up to date.

  3. Manually edit the RHYML draft file.

  4. Generate the release history as Markdown.

    bundle exec rhx docs/releasehx/release-0.2.1.yml --md docs/releasehx/0.2.1.md --config docs/releasehx/config.yml
  5. Copy and paste the contents of docs/releasehx/0.2.1.md into the GitHub release form at https://github.com/DocOps/issuer/releases/new.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/DocOps/issuer.

The gem is available as open source under the terms of the MIT License.

About

A CLI utility for creating GitHub Issues from a single YAML file.

Resources

License

Stars

Watchers

Forks

Packages

No packages published