Skip to main content

Sync JIRA In-Progress activity into Clockify time entries.

Project description

jira-clockify-sync

PyPI version Python versions License: MIT CI

Sync your JIRA "In Progress" activity into Clockify time entries with one command.

Given a date range, the tool finds every JIRA ticket where you were the assignee and the status was "In Progress" during that range, then creates Clockify time entries spreading 8 hours per working day across those tickets.

This is heuristic time-tracking — not auditing. It exists so the calendar shows you've been working, without manual data entry every day.

Prerequisites

Install

From PyPI:

pip install jira-clockify-sync

The package installs the clockify-sync command on your PATH.

For local development (editable install with dev dependencies), see CONTRIBUTING.md.

Configure

cp .env.example .env

Edit .env and fill in the JIRA and Clockify values. The file is gitignored.

To find the Clockify IDs, use:

  • Workspace: GET https://api.clockify.me/api/v1/workspaces with header X-Api-Key: <your-key>
  • Projects in workspace: GET https://api.clockify.me/api/v1/workspaces/{ws}/projects
  • Tags in workspace: GET https://api.clockify.me/api/v1/workspaces/{ws}/tags

Holidays

holidays.yaml is committed and lists the dates excluded from the working calendar. The file ships with Mexico federal holidays for 2026. Format is one ISO date per line, comments allowed:

- 2026-01-01    # Año Nuevo
- 2026-12-25    # Navidad

Maintain this file by hand. The tool does NOT detect holidays automatically. Update before December each year for the upcoming year.

Usage

clockify-sync --from 2026-04-20 --to 2026-04-26

Flags (each has a short alias):

  • --from, -F — start date, ISO format (required).
  • --to, -t — end date, ISO format (required).
  • --dry-run, -d — print the plan, do not write anything to Clockify.
  • --force, -f — replace any prior automation-owned entries in the range.
  • --skip, -s — skip days that already have automation-owned entries.
  • --yes, -y — skip the confirmation prompt before writing.
  • --verbose, -v — DEBUG-level logging.
  • --holidays, -H PATH — alternate holidays file (default: holidays.yaml).

Before writing anything, the CLI prints the plan and asks for confirmation. Use -y to skip the prompt (e.g. in CI). --dry-run never prompts since it never writes.

Idempotency

The tool aborts by default if it finds entries it previously created within the requested range. This protects against accidental double-writes.

It identifies its own entries by three criteria, ALL of which must hold:

  1. The entry's projectId equals CLOCKIFY_PROJECT_ID.
  2. The entry's tagIds contains CLOCKIFY_TAG_ID.
  3. The entry's description matches the regex ^[A-Z][A-Z0-9_]+-\d+( — .*)?$ (e.g., PROJ-123 or PROJ-123 — Fix login).

Any entry failing one of these is considered hand-made and is never deleted, modified, or counted as a conflict — even if it lives in the same project on the same day.

Conflict resolution flags:

Flag Behavior
(none) Abort with error if conflicts are found.
--force Delete the conflicting auto-entries first, then create fresh ones.
--skip Leave days with conflicts untouched, write entries for other days.
--dry-run Read but never write; prints the plan and any conflicts detected.

Timezone

All day-boundary arithmetic uses the timezone in TIMEZONE (default America/Mexico_City). Clockify stores entries in UTC; conversion happens at the boundary. If you change TIMEZONE, re-running for prior dates with --force will resync entries against the new local boundaries.

Mexico City has not observed daylight saving time since 2022, so its offset is a constant -06:00.

Troubleshooting

ValidationError: JIRA_API_TOKEN ... (or any other missing env var). The tool refuses to start without all required env vars set. Check .env is present and populated, or that the env vars are exported in your shell. CLOCKIFY_USER_ID is the only optional one.

JIRA returns 401 / 403. The Basic auth header uses JIRA_EMAIL + JIRA_API_TOKEN. Common causes:

  • Token revoked or expired (regenerate at the API tokens page).
  • Email mismatch — must be the email of the JIRA Cloud account, not an alias.
  • Tenant mismatch — JIRA_BASE_URL must point at the right Atlassian site.

Clockify returns 401 / 403. Confirm CLOCKIFY_API_KEY is the active one in your Clockify profile (the "Generate" button rotates it; old keys stop working immediately).

JIRA / Clockify returns 429 (rate limited). The HTTP clients retry with exponential backoff and honor Retry-After. If the issue persists for a wide range, narrow --from/--to and run in chunks.

zoneinfo._common.ZoneInfoNotFoundError on Windows. Windows has no system tzdata. The tzdata PyPI package is declared as a Windows-only runtime dependency and should install automatically. Re-run pip install -e ".[dev]" if you skipped it.

Tickets that should be detected aren't appearing. Detection requires (a) you were the assignee at the moment the status was "In Progress" and (b) those moments fall inside the requested range. If a ticket sat in "In Progress" while assigned to someone else, it won't count. Run with --verbose and check the JQL it issues.

A ticket appears on a day I didn't touch it. By design. The "presence" definition counts a day for a ticket if its (assignee=you ∧ status="In Progress") interval intersects any moment of that day. Long-running In-Progress tickets fill the day automatically.

More than 8 tickets active in one day. The first 8 alphabetically by key receive 1 hour each, and the rest are skipped with a warning that names them. There are no fractional-hour blocks.

Moving to GitHub Actions

The tool reads only env vars at runtime, so the same code runs unmodified under GitHub Actions.

Step 1 — Define repo secrets and variables

Settings → Secrets and variables → Actions:

Secrets (sensitive — never logged in plaintext):

  • JIRA_API_TOKEN
  • CLOCKIFY_API_KEY

Variables (non-sensitive — visible in run logs):

  • JIRA_BASE_URL
  • JIRA_EMAIL
  • CLOCKIFY_WORKSPACE_ID
  • CLOCKIFY_PROJECT_ID
  • CLOCKIFY_TAG_ID
  • CLOCKIFY_USER_ID (optional)
  • TIMEZONE (optional; defaults to America/Mexico_City)

Step 2 — Add a workflow

Use a workflow_dispatch trigger so you can fire it manually with a date range:

# .github/workflows/sync.yml — example, NOT yet added by this milestone
name: Clockify sync
on:
  workflow_dispatch:
    inputs:
      from: { description: 'YYYY-MM-DD', required: true }
      to:   { description: 'YYYY-MM-DD', required: true }
      mode:
        description: 'error | force | skip | dry-run'
        default: 'error'

jobs:
  sync:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - run: pip install -e .
      - env:
          JIRA_BASE_URL:        ${{ vars.JIRA_BASE_URL }}
          JIRA_EMAIL:           ${{ vars.JIRA_EMAIL }}
          JIRA_API_TOKEN:       ${{ secrets.JIRA_API_TOKEN }}
          CLOCKIFY_API_KEY:     ${{ secrets.CLOCKIFY_API_KEY }}
          CLOCKIFY_WORKSPACE_ID: ${{ vars.CLOCKIFY_WORKSPACE_ID }}
          CLOCKIFY_PROJECT_ID:  ${{ vars.CLOCKIFY_PROJECT_ID }}
          CLOCKIFY_TAG_ID:      ${{ vars.CLOCKIFY_TAG_ID }}
          TIMEZONE:             ${{ vars.TIMEZONE }}
          CI:                   'true'
        run: |
          flag=""
          case "${{ inputs.mode }}" in
            force)   flag="--force" ;;
            skip)    flag="--skip" ;;
            dry-run) flag="--dry-run" ;;
          esac
          clockify-sync --from "${{ inputs.from }}" --to "${{ inputs.to }}" $flag

The workflow file itself is intentionally not part of the initial milestone — add it when you're ready to migrate.

Contributing

See CONTRIBUTING.md for development setup, the quality checks the project enforces, and the release process.

Hot-path coverage targets: allocator.py and jira/timeline.py should stay above 90%. Adding new branches without tests is a smell.

Changelog

See CHANGELOG.md for the release history.

License

MIT © 2026 Francisco Castellanos

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

jira_clockify_sync-0.1.0.tar.gz (23.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

jira_clockify_sync-0.1.0-py3-none-any.whl (21.4 kB view details)

Uploaded Python 3

File details

Details for the file jira_clockify_sync-0.1.0.tar.gz.

File metadata

  • Download URL: jira_clockify_sync-0.1.0.tar.gz
  • Upload date:
  • Size: 23.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for jira_clockify_sync-0.1.0.tar.gz
Algorithm Hash digest
SHA256 da958ebc4d8d8316d39e507f85864072dc98709ec77e5aec7fc5e6027daf1849
MD5 fd98f57e7d1f63bf9101c38ba24f65c1
BLAKE2b-256 1a5a26b86f8bf5ced6823a7f40ef5fa9211188123068e46880afb04326b79e82

See more details on using hashes here.

Provenance

The following attestation bundles were made for jira_clockify_sync-0.1.0.tar.gz:

Publisher: publish.yml on ing-fcastellanos/clockify-automation

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file jira_clockify_sync-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for jira_clockify_sync-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5c0a720da110314cbf0afe4ef05a6aa5a7c67ac881a6039a6886f9564c03ebe3
MD5 134ea32576373a84c6296a335f2f80ff
BLAKE2b-256 9160f9a8f4a944bc4574504a2ec9916c798c55080509312b801ea5390704e86b

See more details on using hashes here.

Provenance

The following attestation bundles were made for jira_clockify_sync-0.1.0-py3-none-any.whl:

Publisher: publish.yml on ing-fcastellanos/clockify-automation

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page