Seal Logo GitHub Logo

Seal GitHub App for Change Control

Connect GitHub with Seal for automated change control and compliance in regulated environments

Built for Regulated Software Development

This integration bridges the gap between modern development workflows and strict regulatory requirements, designed specifically for organizations in regulated industries.

Software as Medical Device (SaMD)

Meet FDA requirements with comprehensive documentation of all code changes. Automatically create and maintain traceability between requirements, change orders, and code implementations to satisfy 21 CFR Part 820 and IEC 62304 compliance.

GxP Regulated Environments

Ensure compliance with Good Practice regulations including 21 CFR Part 11, EU Annex 11, and ISO 13485 by maintaining complete, timestamped audit trails of every change with proper review and approval workflows.

Integration Features

Choose which GitHub integrations you want to set up with Seal to simplify your development and compliance workflow:

Pull Request Integration

Automatically creates Seal change control instances from GitHub PRs. When the Change Order is approved in Seal, the PR is automatically approved in GitHub.

  • Bidirectional sync between PRs and Change Orders
  • Automated approval process enforcement
  • Complete traceability from code changes to approvals
  • Release artifact tracking for build outputs
  • Simple codebase snapshots for compliant versioning

Issue Tracking

Syncs GitHub Issues with Seal for defect tracking. Labels specific issues to be synced and keeps both systems updated as changes occur.

  • Automatic creation of defects in Seal from GitHub issues
  • Status synchronization between systems
  • Configurable with label-based filtering

Implement Now

Follow these step-by-step instructions to integrate GitHub with Seal and streamline your regulatory compliance workflow.

Getting Started: This comprehensive setup guide includes core functionality and optional features. Each optional component is clearly marked, so you can focus on just the parts you need.
Before You Begin: You'll need admin access to both your GitHub organization and Seal account.

Prepare Your Seal Environment

First, configure your Seal account with the necessary templates and permissions:

  1. Create a Change Control Template in Seal with these fields:
    • brief_description - Text field for PR title/description
    • pr_number - Text field for GitHub PR number
    • pr_link - Text field for GitHub PR URL
    • repo_name - Text field for GitHub repository ID
    • repo_name - Text field for GitHub repository name
    • pr_title - Text field for PR title
    • approved_in_github - Boolean field to track GitHub approval status

    Important: Configure a Computed Title to ensure reliable entity search:

    • Open your template settings and locate the "Computed Title" section
    • Set the format to: ${repo_name}-${pr_number}
    • This ensures entities have a unique, stable identifier even if PR titles change
    • Required for bidirectional sync: This format enables automatic PR approval when the corresponding Seal document is approved
  2. Create an Issue Tracking Template (optional) with these fields:
    • issue_number - Text field for GitHub issue number
    • issue_title - Text field for issue title
    • issue_description - Text field for issue body
    • issue_link - Text field for GitHub issue URL
    • repo_name - Text field for GitHub repository name
    • issue_labels - Text field for issue labels

    Important: Configure a Computed Title for issue tracking too:

    • Open your template settings and locate the "Computed Title" section
    • Set the format to: ${repo_name}-${issue_number}
    • This enables reliable entity lookup and prevents duplicate entities
  3. Publish both templates - Templates must be published to be accessible via API
  4. Generate an API token:
    • Go to Organization Settings → API Tokens
    • Click "Create New Token" and give it a descriptive name
    • Ensure it has proper permissions to create and update entities
    • Copy and securely store the generated token
  5. Collect Template IDs:
    • Open each template and copy the ID from the URL
    • The format is similar to: 3eecf18f-da15-429c-968a-df43ba85c970
  6. Note your Organization ID from the URL when viewing your Seal organization
Field names must match exactly what's specified in the configuration.

Install the GitHub App

  1. Go to the GitHub Marketplace:
  2. Choose Installation Scope:
    • Select "All repositories" or choose specific repositories
    • For a production setup, we recommend starting with a single test repository
  3. Authorize the Installation with your GitHub credentials
  4. Verify Installation in your GitHub organization settings under "GitHub Apps"
The GitHub App requires specific permissions to function correctly. Review the permissions during installation.

Create Basic Configuration File

Set up the configuration file in your GitHub repository:

  1. Create a .github directory in your repository if it doesn't exist
  2. Create a configuration file at .github/seal-change-control-config.yml
  3. Add your basic configuration using the template below:
# .github/seal-change-control-config.yml
# Basic Configuration (Required)
isTesting: true  # Keep true during initial setup
sourceBranchRegex: "^rc/(\\d+)\\.(\\d+)$"
targetBranchRegex: "^r(\\d+)\\.x$"
sealTemplateId: "your-change-control-template-id"
sealApiBaseUrl: "https://us.backend.seal.run/api/"
sealApiToken: "your-seal-api-token"
orgId: "your-org-id"
platformBaseUrl: "us.platform.seal.run"

# Issue Tracking Configuration (Optional)
issueSyncTemplateId: "your-issue-template-id"
issueLabelRegex: "(bug|defect)"

Upload Codebase Snapshots to Seal

For regulated environments, you'll often need to capture and preserve the exact code state at the time of a change request. This GitHub Action example demonstrates how to:

  • Create a snapshot of your codebase for each PR
  • Upload the snapshot directly to your Seal change control document
  • Maintain a complete audit trail for compliance and traceability

1. Create a reference field in Seal

First, add a field to your change control template in Seal:

  1. Navigate to your template in Seal and add a new field
  2. Create a field named code_snapshot with type "Reference"
  3. Enable "Allow Multiple" if you want to store multiple snapshots
  4. Set appropriate permissions and save your template

2. Create a GitHub Actions workflow file

Add this workflow file to your repository at .github/workflows/codebase_snapshot.yml:

# .github/workflows/codebase_snapshot.yml
name: Create Codebase Snapshot for Seal

on:
  pull_request:
    types: [opened, synchronize, reopened]

jobs:
  codebase-snapshot:
    runs-on: ubuntu-latest
    steps:
      # Step 1: Checkout repository first to access files
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0  # Get all history for accurate snapshot
          
      # Step 2: Check if this PR matches our branch pattern requirements
      - name: Check branch patterns
        id: check-branches
        run: |
          SOURCE_BRANCH="${{ github.head_ref }}"
          TARGET_BRANCH="${{ github.base_ref }}"
          
          # Default patterns
          SOURCE_PATTERN="^rc/"
          TARGET_PATTERN="^r.*\.x$"
          
          # Try to read patterns from config if file exists
          CONFIG_FILE=".github/seal-change-control-config.yml"
          if [ -f "$CONFIG_FILE" ]; then
            echo "Reading branch patterns from config file"
            
            # Extract source pattern
            SOURCE_PATTERN_LINE=$(grep "sourceBranchRegex:" "$CONFIG_FILE" || echo "")
            if [ -n "$SOURCE_PATTERN_LINE" ]; then
              SOURCE_EXTRACT=$(echo "$SOURCE_PATTERN_LINE" | sed -E 's/.*"([^"]*)".*|.*'\''([^'\'']*)'\''.*/\1\2/' || echo "")
              if [ -n "$SOURCE_EXTRACT" ]; then
                SOURCE_PATTERN="$SOURCE_EXTRACT"
                echo "Found source pattern: $SOURCE_PATTERN"
              fi
            fi
            
            # Extract target pattern
            TARGET_PATTERN_LINE=$(grep "targetBranchRegex:" "$CONFIG_FILE" || echo "")
            if [ -n "$TARGET_PATTERN_LINE" ]; then
              TARGET_EXTRACT=$(echo "$TARGET_PATTERN_LINE" | sed -E 's/.*"([^"]*)".*|.*'\''([^'\'']*)'\''.*/\1\2/' || echo "")
              if [ -n "$TARGET_EXTRACT" ]; then
                # Normalize pattern for bash compatibility
                TARGET_PATTERN=$(echo "$TARGET_EXTRACT" | sed 's/\\\\/\\/g')
                TARGET_PATTERN=$(echo "$TARGET_PATTERN" | sed 's/\\d/[0-9]/g')
                echo "Found target pattern: $TARGET_EXTRACT"
              fi
            fi
          else
            echo "Config file not found, using default patterns"
          fi
          
          echo "Using patterns: SOURCE=$SOURCE_PATTERN TARGET=$TARGET_PATTERN"
          
          # Check if branches match patterns
          if [[ "$SOURCE_BRANCH" =~ $SOURCE_PATTERN ]] && [[ "$TARGET_BRANCH" =~ $TARGET_PATTERN ]]; then
            echo "matched=true" >> $GITHUB_OUTPUT
            echo "Branch patterns match! Creating codebase snapshot."
          else
            echo "matched=false" >> $GITHUB_OUTPUT
            echo "Branch patterns don't match. Skipping snapshot creation."
          fi
        shell: bash
        
      # Step 3: Create a ZIP archive with metadata
      - name: Create codebase archive
        if: steps.check-branches.outputs.matched == 'true'
        id: create-archive
        run: |
          mkdir -p snapshot
          
          # Generate a unique filename with PR number and timestamp
          TIMESTAMP=$(date +%Y%m%d%H%M%S)
          PR_NUMBER=${{ github.event.pull_request.number }}
          ARCHIVE_NAME="codebase-pr-${PR_NUMBER}-${TIMESTAMP}.zip"
          
          # Create a metadata file to include in the archive
          cat > snapshot/VERSION.txt << EOF
          Repository: ${{ github.repository }}
          PR Number: ${{ github.event.pull_request.number }}
          PR Title: ${{ github.event.pull_request.title }}
          Source Branch: ${{ github.head_ref }}
          Target Branch: ${{ github.base_ref }}
          Commit: ${{ github.sha }}
          Created: $(date -u +"%Y-%m-%d %H:%M:%S UTC")
          
          # Commit History
          $(git log -n 10 --pretty=format:"%h - %an, %ar : %s")
          EOF
          
          # Create ZIP with codebase and metadata
          zip -r "snapshot/${ARCHIVE_NAME}" . -x "node_modules/*" ".git/*" ".github/workflows/*" \
            "dist/*" "build/*" "*.zip" "*.tar.gz"
            
          # Set outputs for use in later steps
          echo "ARCHIVE_NAME=${ARCHIVE_NAME}" >> $GITHUB_ENV
          echo "archive_name=${ARCHIVE_NAME}" >> $GITHUB_OUTPUT
          
          echo "Created codebase snapshot: ${ARCHIVE_NAME}"
          
      # Step 4: Check if Seal API config exists before proceeding
      - name: Check Seal API configuration
        if: steps.check-branches.outputs.matched == 'true'
        id: check-api-config
        run: |
          CONFIG_FILE=".github/seal-change-control-config.yml"
          if [ -f "$CONFIG_FILE" ]; then
            # Extract API URL and token
            API_URL=$(grep "sealApiBaseUrl:" "$CONFIG_FILE" | sed -E 's/sealApiBaseUrl:[ \t]*"?([^"#[:space:]]+)"?.*/\1/' || echo "")
            API_TOKEN=$(grep "sealApiToken:" "$CONFIG_FILE" | sed -E 's/sealApiToken:[ \t]*"?([^"#[:space:]]+)"?.*/\1/' || echo "")
            
            # Validate API URL
            if [ -n "$API_URL" ] && [[ "$API_URL" == http* ]]; then
              echo "api_url_valid=true" >> $GITHUB_OUTPUT
              echo "SEAL_API_URL=$API_URL" >> $GITHUB_ENV
              echo "Found API URL: $API_URL"
            else
              echo "api_url_valid=false" >> $GITHUB_OUTPUT
              echo "Invalid or missing API URL"
            fi
            
            # Validate API token
            if [ -n "$API_TOKEN" ] && [[ "$API_TOKEN" != "your-seal-api-token" ]]; then
              echo "api_token_valid=true" >> $GITHUB_OUTPUT
              echo "SEAL_API_TOKEN=$API_TOKEN" >> $GITHUB_ENV
              echo "Found valid API token"
            else
              echo "api_token_valid=false" >> $GITHUB_OUTPUT
              echo "Invalid or missing API token"
            fi
            
            # Set overall config validity
            if [ -n "$API_URL" ] && [ -n "$API_TOKEN" ]; then
              echo "config_valid=true" >> $GITHUB_OUTPUT
              echo "✅ Valid Seal API configuration found."
            else
              echo "config_valid=false" >> $GITHUB_OUTPUT
              echo "⚠️ Invalid or incomplete Seal API configuration."
            fi
          else
            echo "config_valid=false" >> $GITHUB_OUTPUT
            echo "⚠️ Seal configuration file not found."
          fi
        shell: bash
          
      # Step 5: Get Seal entity ID from PR title pattern
      - name: Find Seal entity
        if: steps.check-branches.outputs.matched == 'true' && steps.check-api-config.outputs.config_valid == 'true'
        id: find-entity
        run: |
          # Make sure API URL ends with a slash
          [[ "$SEAL_API_URL" != */ ]] && SEAL_API_URL="${SEAL_API_URL}/"
          
          # Format: repo_name-pr_number (the Computed Title format in Seal)
          REPO_NAME=$(echo "${{ github.repository }}" | cut -d '/' -f 2)
          PR_NUMBER=${{ github.event.pull_request.number }}
          ENTITY_TITLE="${REPO_NAME}-${PR_NUMBER}"
          
          echo "Searching for Seal entity with title: ${ENTITY_TITLE}"
          
          # Search for entity by title
          SEARCH_RESPONSE=$(curl -s -H "Authorization: Bearer ${SEAL_API_TOKEN}" \
            "${SEAL_API_URL}entities/search?title=${ENTITY_TITLE}")
          
          # Extract entity ID from first result
          ENTITY_ID=$(echo ${SEARCH_RESPONSE} | jq -r '.[0].id')
          
          if [[ "${ENTITY_ID}" == "null" || -z "${ENTITY_ID}" ]]; then
            echo "No Seal entity found with title: ${ENTITY_TITLE}"
            echo "entity_found=false" >> $GITHUB_OUTPUT
          else
            echo "Found Seal entity with ID: ${ENTITY_ID}"
            echo "entity_id=${ENTITY_ID}" >> $GITHUB_OUTPUT
            echo "entity_found=true" >> $GITHUB_OUTPUT
          fi
          
      # Step 6: Upload ZIP archive to Seal
      - name: Upload codebase archive to Seal
        if: steps.check-branches.outputs.matched == 'true' && steps.check-api-config.outputs.config_valid == 'true' && steps.find-entity.outputs.entity_found == 'true'
        id: upload-file
        run: |
          # Make sure API URL ends with a slash
          [[ "$SEAL_API_URL" != */ ]] && SEAL_API_URL="${SEAL_API_URL}/"
          
          # Upload the file to Seal's files API
          echo "Uploading codebase archive: ${ARCHIVE_NAME}"
          
          # Use the specific typeTitle for file categorization
          TYPE_TITLE="github_release_artifacts"
          ENCODED_FILENAME=$(echo "${ARCHIVE_NAME}" | sed 's/ /%20/g')
          
          FILE_RESPONSE=$(curl -s -X POST \
            -H "Authorization: Bearer ${SEAL_API_TOKEN}" \
            -H "Content-Type: application/octet-stream" \
            --data-binary "@snapshot/${ARCHIVE_NAME}" \
            "${SEAL_API_URL}files?filename=${ENCODED_FILENAME}&typeTitle=${TYPE_TITLE}")
          
          # Extract file entity ID
          FILE_ID=$(echo "$FILE_RESPONSE" | jq -r '.id // "null"')
          
          if [[ "${FILE_ID}" != "null" && -n "${FILE_ID}" ]]; then
            echo "✅ Successfully uploaded codebase snapshot with ID: ${FILE_ID}"
            echo "file_id=${FILE_ID}" >> $GITHUB_OUTPUT
          else
            echo "❌ File upload failed."
            echo ""
            echo "You may need to create a file type 'github_release_artifacts' in your Seal organization."
            exit 1
          fi
          
      # Step 7: Update reference field in Seal entity
      - name: Link file to entity
        if: steps.check-branches.outputs.matched == 'true' && steps.check-api-config.outputs.config_valid == 'true' && steps.find-entity.outputs.entity_found == 'true' && steps.upload-file.outputs.file_id != ''
        run: |
          # Make sure API URL ends with a slash
          [[ "$SEAL_API_URL" != */ ]] && SEAL_API_URL="${SEAL_API_URL}/"
          
          # Get entity ID and file ID
          ENTITY_ID="${{ steps.find-entity.outputs.entity_id }}"
          FILE_ID="${{ steps.upload-file.outputs.file_id }}"
          
          # Get file entity details to ensure proper reference format
          FILE_ENTITY=$(curl -s \
            -H "Authorization: Bearer ${SEAL_API_TOKEN}" \
            "${SEAL_API_URL}entities/${FILE_ID}")
            
          # Extract version if available
          if echo "$FILE_ENTITY" | jq . >/dev/null 2>&1; then
            VERSION=$(echo "$FILE_ENTITY" | jq -r '.version // null')
            echo "File entity version: ${VERSION}"
          else
            VERSION=null
            echo "Could not get file version, using null"
          fi
          
          # Create properly formatted reference object
          REFERENCE_OBJECT="{\"id\":\"${FILE_ID}\",\"version\":${VERSION}}"
          
          # Try to update the field with an array of references
          echo "Linking file to change control document..."
          UPDATE_RESPONSE=$(curl -s -X PATCH \
            -H "Authorization: Bearer ${SEAL_API_TOKEN}" \
            -H "Content-Type: application/json" \
            -d "{\"value\": [${REFERENCE_OBJECT}]}" \
            "${SEAL_API_URL}entities/${ENTITY_ID}/fields/code_snapshot")
            
          # Check for success, try alternative formats if needed
          if echo "${UPDATE_RESPONSE}" | jq . >/dev/null 2>&1 && ! echo "${UPDATE_RESPONSE}" | jq -e '.message' >/dev/null 2>&1; then
            echo "✅ Successfully linked codebase snapshot to Seal change control document"
          else
            echo "⚠️ Could not update field with array format, trying alternatives"
            
            # Try as single reference object
            UPDATE_RESPONSE=$(curl -s -X PATCH \
              -H "Authorization: Bearer ${SEAL_API_TOKEN}" \
              -H "Content-Type: application/json" \
              -d "{\"value\": ${REFERENCE_OBJECT}}" \
              "${SEAL_API_URL}entities/${ENTITY_ID}/fields/code_snapshot")
              
            if echo "${UPDATE_RESPONSE}" | jq . >/dev/null 2>&1 && ! echo "${UPDATE_RESPONSE}" | jq -e '.message' >/dev/null 2>&1; then
              echo "✅ Successfully linked codebase snapshot to Seal change control document"
            else
              echo "⚠️ Linking attempt failed. File was uploaded but field update failed."
              echo "File ID for manual linking: ${FILE_ID}"
            fi
          fi

3. How this workflow works

This GitHub Action performs these key steps:

  1. Checks out the repository to access configuration files
  2. Checks if the PR matches your configured branch patterns
  3. Creates a ZIP archive containing your codebase and a metadata file
  4. Validates that Seal API configuration exists and is valid
  5. Finds the corresponding Seal entity using the computed title format
  6. Uploads the ZIP archive to Seal's file storage as a specific file type
  7. Links the file to your change control document via a reference field
Note: This workflow automatically finds the correct Seal entity by using the ${repo_name}-${pr_number} format. This is why setting up the Computed Title format in your Seal template is essential.
Important: You may need to create a file type called github_release_artifacts in your Seal organization admin panel under Settings > Types before running this workflow.

Test the Integration

Verify everything works by testing the complete PR workflow:

  1. Create a test branch that matches your sourceBranchRegex pattern
  2. Make a simple change and commit it to your test branch
  3. Create a pull request targeting a branch that matches your targetBranchRegex
  4. Wait for GitHub Actions to complete if you're testing artifact tracking
  5. Check the PR comments - You should see a comment with a link to the Seal change control instance
  6. Verify in Seal that a new change order was created with the correct information

Verification checklist:

☐ GitHub App is properly installed and authorized
☐ PR creates a comment with a link to the Seal change control instance
☐ Change order contains correct PR information
☐ Issue tracking works (if configured)
☐ Artifact tracking works (if configured)
☐ Branch pattern matching works as expected
Testing in isTesting: true mode provides detailed logs to help troubleshoot any issues.

Troubleshooting

Common Installation Issues
  • PR doesn't create a Seal change control instance
    • Verify branch patterns match your sourceBranchRegex and targetBranchRegex
    • Check that your Seal API token has proper permissions
    • Ensure the template ID is correct and the template is published
    • Check GitHub App logs for detailed error messages
  • Template not found errors
    • Confirm your template IDs are correct in the configuration
    • Make sure templates are published in Seal, not just saved as drafts
  • Field updates fail
    • Verify field names match exactly what's in your configuration
    • Check field permissions in Seal to ensure they allow API updates
  • Authentication failures
    • Regenerate your API token and update the configuration
    • Verify the API token hasn't expired
    • Confirm the sealApiBaseUrl is correct for your region
Viewing Logs

When isTesting: true is set, detailed logs will be available:

  • Check PR comments for detailed debugging information
  • GitHub Action logs will contain integration diagnostic messages
  • For detailed API interaction logs, contact Seal support

Production Deployment

Once you've verified everything works correctly, finalize your production setup:

  1. Update configuration to disable test mode:
    isTesting: false  # Change to false for production use
  2. Consider security best practices:
    • Limit repository access to authorized personnel
    • Implement regular API token rotation
  3. Extend to additional repositories as needed
  4. Document your configuration for your organization
  5. Train your team on how to use the integration
Looking for future enhancements? Contact your Seal representative for information about upcoming features on our roadmap.