Sync JIRA In-Progress activity into Clockify time entries.
Project description
jira-clockify-sync
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
- Python 3.11 or newer (tested on 3.14)
- A JIRA Cloud account with an API token (https://id.atlassian.com/manage-profile/security/api-tokens)
- A Clockify account with an API key (https://app.clockify.me/user/settings → API)
- The IDs of the workspace, project, and tag you use in Clockify
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/workspaceswith headerX-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:
- The entry's
projectIdequalsCLOCKIFY_PROJECT_ID. - The entry's
tagIdscontainsCLOCKIFY_TAG_ID. - The entry's
descriptionmatches the regex^[A-Z][A-Z0-9_]+-\d+( — .*)?$(e.g.,PROJ-123orPROJ-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_URLmust 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_TOKENCLOCKIFY_API_KEY
Variables (non-sensitive — visible in run logs):
JIRA_BASE_URLJIRA_EMAILCLOCKIFY_WORKSPACE_IDCLOCKIFY_PROJECT_IDCLOCKIFY_TAG_IDCLOCKIFY_USER_ID(optional)TIMEZONE(optional; defaults toAmerica/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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
da958ebc4d8d8316d39e507f85864072dc98709ec77e5aec7fc5e6027daf1849
|
|
| MD5 |
fd98f57e7d1f63bf9101c38ba24f65c1
|
|
| BLAKE2b-256 |
1a5a26b86f8bf5ced6823a7f40ef5fa9211188123068e46880afb04326b79e82
|
Provenance
The following attestation bundles were made for jira_clockify_sync-0.1.0.tar.gz:
Publisher:
publish.yml on ing-fcastellanos/clockify-automation
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
jira_clockify_sync-0.1.0.tar.gz -
Subject digest:
da958ebc4d8d8316d39e507f85864072dc98709ec77e5aec7fc5e6027daf1849 - Sigstore transparency entry: 1398106547
- Sigstore integration time:
-
Permalink:
ing-fcastellanos/clockify-automation@4f079fecdeab11a827611467a191dc4ffd2e2172 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/ing-fcastellanos
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4f079fecdeab11a827611467a191dc4ffd2e2172 -
Trigger Event:
push
-
Statement type:
File details
Details for the file jira_clockify_sync-0.1.0-py3-none-any.whl.
File metadata
- Download URL: jira_clockify_sync-0.1.0-py3-none-any.whl
- Upload date:
- Size: 21.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5c0a720da110314cbf0afe4ef05a6aa5a7c67ac881a6039a6886f9564c03ebe3
|
|
| MD5 |
134ea32576373a84c6296a335f2f80ff
|
|
| BLAKE2b-256 |
9160f9a8f4a944bc4574504a2ec9916c798c55080509312b801ea5390704e86b
|
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
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
jira_clockify_sync-0.1.0-py3-none-any.whl -
Subject digest:
5c0a720da110314cbf0afe4ef05a6aa5a7c67ac881a6039a6886f9564c03ebe3 - Sigstore transparency entry: 1398106553
- Sigstore integration time:
-
Permalink:
ing-fcastellanos/clockify-automation@4f079fecdeab11a827611467a191dc4ffd2e2172 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/ing-fcastellanos
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4f079fecdeab11a827611467a191dc4ffd2e2172 -
Trigger Event:
push
-
Statement type: