Skip to content

feat(toggl): add Toggl Track integration - #672

Merged
devjain32 merged 8 commits into
corsairdev:mainfrom
Agam00:feat/toggl-plugin
Aug 12, 2026
Merged

feat(toggl): add Toggl Track integration#672
devjain32 merged 8 commits into
corsairdev:mainfrom
Agam00:feat/toggl-plugin

Conversation

@Agam00

@Agam00 Agam00 commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Description

Adds a Toggl Track integration implemented against the Track API v9.

Fixes #671

73 operations across 11 resource groups, each with zod input and output
schemas, a declared risk level and a description. This covers the full 56-op
surface listed on corsair.dev/oss/toggl, plus a handful of natural companions
(update/delete on projects, tasks and time entries) that the catalog omits:

Group Ops Operations
me 13 get, update, get/updatePreferences, logged, location, quota, clients, projects, tags, tasks, disableProductEmails, disableWeeklyReport
workspaces 6 list, get, update, getUsers, getLogo, getPreferences
organizations 11 get, update, create, getWorkspaces, getGroups, createGroup, deleteGroup, getUsers, createInvitation, getPlans, getSubscriptionPlans
clients 5 list, get, create, update, delete
projects 7 list, get, create, update, delete, addUser, deleteGroup
tasks 6 list, listWorkspace, get, create, update, delete
tags 4 list, create, update, delete
timeEntries 8 list, getCurrent, get, create, update, stop, delete, bulkEdit
reference 6 countries, countrySubdivisions, currencies, timezones, timezoneOffsets, keys
webhooks 4 getStatus, getEventFilters, listSubscriptions, deleteSubscription
smail 3 sendDemo, sendContact, sendMeet

Auth

Toggl uses HTTP Basic with the API token as the username and the literal string
api_token as the password. It is a per-user token with no OAuth flow and no
refresh/expiry lifecycle, so it maps onto Corsair's api_key auth type directly
and oauth_2 is not offered.

Rate limiting and errors

Toggl paces requests at roughly 1/sec per token per IP using a leaky bucket and
returns 429 on overflow, without a documented Retry-After. The client is
configured for 5 retries with 1s initial delay and 2x backoff, and every failure
routes through error-handlers.ts (429, auth, permission, not-found, validation,
network, default).

One quirk worth reviewer attention: Toggl answers a bad or revoked token with
403, not 401
, so status alone cannot separate an auth failure from a permission
failure. The auth and permission handlers match on the response body to tell them
apart.

Schema design

Only the slow-changing structural records are persisted — workspaces,
clients, projects, tags. These resolve the ids nearly every other call
needs, change rarely, and caching them avoids spending the 1/sec budget on
lookups.

Time entries are deliberately not persisted: they are high-volume, they
mutate while a timer is running, and they are almost always wanted as a live view
rather than a stale local copy.

Scope notes

  • Webhook subscription management is included; Corsair webhook triggers are
    not.
    The catalog lists 0 triggers, so no handlers are registered, but the
    subscription/status/event-filter operations are implemented against Toggl's
    separate /webhooks/api/v1 host. GET /event_filters confirms Toggl emits
    created/updated/deleted for client, project and time_entry, so the payload
    envelope and tenant matcher are left in place to make adding triggers additive.
  • The transactional mail operations (smail.*) and the two email-unsubscribe
    operations are implemented and covered by mocked tests, but deliberately never
    fired against the live API — doing so would send real email.
  • Reports API v3 sits on a separate base path with its own pagination model
    and is out of scope here.
  • No new third-party dependencies.

Checklist

Before submitting your PR, please verify the following:

  • I have run pnpm lint and all checks pass
  • I have run pnpm typecheck and there are no TypeScript errors
  • I have run pnpm build and all packages build successfully
  • I have run pnpm test and all tests pass
  • I have added or updated tests where applicable
  • I have added or updated necessary documentation

Screenshots / Demos (if applicable)

120 tests total — 111 CI-safe + 9 live.

integration.test.ts runs against a real Toggl account and performs genuine
round-trips: create/read/delete a client, create/list/delete a project,
create/rename/delete a tag, and start/stop/delete a running timer — each
validating the live response against the declared schema and cleaning up after
itself. Requests are paced at 1.1s for the leaky bucket.

Screenshot 2026-08-12 154705

The 111 CI-safe tests (schema.test.ts, client.test.ts, endpoints.test.ts,
endpoints-extended.test.ts)
cover schema validation against captured real payloads, Basic auth header
construction, every error handler branch, and all 73 endpoint wrappers — their
paths, methods, query strings, request bodies, response normalisation and local
cache writes — with the network mocked.

Additional Notes

  • integration.test.ts is named to match the CI exclusion in pr-checks.yml, so
    it never runs without credentials. It also self-skips when TOGGL_API_TOKEN is
    absent. Run it locally with:
    TOGGL_API_TOKEN=<token> TOGGL_WORKSPACE_ID=<id> pnpm exec jest integration
  • The plugin is not registered in demo/testing/. CONTRIBUTING.md asks for
    that, but R1 in PLUGIN_PR_RULES.md restricts a plugin PR to
    packages/<plugin>/**, the constants.ts registration and pnpm-lock.yaml
    so committing it would fail the scope gate. Live verification lives in
    integration.test.ts instead. Flagging in case the two documents should be
    reconciled.
  • Minor observation, not addressed here since it is outside this PR's scope:
    getResponseBody in packages/corsair/async-core/request.ts logs a caught
    SyntaxError to console.error when a provider returns an empty body with a
    JSON content type. Toggl does this on every successful DELETE, so the noise
    shows up in test output even though the behaviour is correct.
  • Anyone testing this from a brand new Toggl account should know that workspaces
    created in the new Toggl app are not visible to the Track v9 API. A workspace
    has to exist via POST /api/v9/organizations for any workspace-scoped endpoint
    to return data.

Summary by CodeRabbit

  • New Features
    • Added Toggl integration with authenticated API access and retry handling.
    • Added support for workspaces, organizations, clients, projects, tasks, tags, time entries, reference data, and webhook subscriptions.
    • Added Toggl resource validation, caching, tenant matching, and secure audit logging.
    • Added Agenty and Toggl as supported providers.
  • Tests
    • Added comprehensive coverage for API requests, endpoint behavior, schemas, error handling, and integration workflows.

@vercel

vercel Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

@Agam00 is attempting to deploy a commit to the corsair Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions github-actions Bot added the core Changes in packages/corsair label Aug 12, 2026
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR adds a Toggl Track provider with API v9 support, typed endpoint schemas, CRUD operations, API-key authentication, error handling, webhook tenant matching, persistence, package configuration, integration tests, and provider registration.

Changes

Toggl Track provider

Layer / File(s) Summary
API contracts and persistence
packages/toggl/endpoints/types.ts, packages/toggl/schema/*, packages/toggl/endpoints/persist.ts
Adds Zod schemas, endpoint input/output registries, deletion results, and persisted workspace, client, project, and tag entities.
API transport and error handling
packages/toggl/client.ts, packages/toggl/error-handlers.ts, packages/toggl/client.test.ts
Adds Basic authentication, API requests, query and JSON support, rate-limit retries, and categorized error handlers.
Endpoint operations
packages/toggl/endpoints/*
Adds typed handlers for account, workspaces, organizations, clients, projects, tasks, tags, time entries, reference data, webhooks, and transactional mail.
Plugin and webhook wiring
packages/toggl/index.ts, packages/toggl/endpoints/index.ts, packages/toggl/webhooks/*
Registers endpoint schemas and metadata, configures API-key authentication, exports types, and matches webhook tenants by workspace ID.
Validation and package setup
packages/toggl/*, packages/corsair/core/constants.ts
Adds mocked and live integration tests, package tooling, persisted schema registration, and Toggl provider declarations.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CorsairPlugin
  participant TogglEndpoint
  participant makeTogglRequest
  participant TogglAPI
  CorsairPlugin->>TogglEndpoint: Invoke typed endpoint
  TogglEndpoint->>makeTogglRequest: Send path, method, body, and query
  makeTogglRequest->>TogglAPI: Send Basic-authenticated API request
  TogglAPI-->>makeTogglRequest: Return JSON response or API error
  makeTogglRequest-->>TogglEndpoint: Return parsed result or classified error
  TogglEndpoint-->>CorsairPlugin: Return typed output
Loading
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR adds unrelated Toggl transactional email endpoints that are not part of the linked issue objectives. Remove the Smail transactional email endpoints, or link a separate issue that explicitly requires them.
Docstring Coverage ⚠️ Warning Docstring coverage is 52.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the primary change: adding a Toggl Track integration.
Linked Issues check ✅ Passed The implementation covers the requested Track API v9 plugin, authentication, retries, core resources, schemas, and webhook subscription scope [#671].
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Agam00
Agam00 marked this pull request as ready for review August 12, 2026 10:25
@greptile-apps

greptile-apps Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The follow-up adds the missing endpoint-level tests, credential redaction, and entity-cache synchronization requested in prior review.

  • Replaces the credential-shaped test token with an unmistakably synthetic fixture.
  • Removes api_token from both profile endpoint results.
  • Synchronizes cached workspaces, clients, projects, and tags across direct and alternate listing paths.
  • Refreshes or evicts clients after archive responses that contain only affected IDs.
  • Adds direct coverage for the Toggl endpoint wrappers, including me.getTags.

Confidence Score: 5/5

The PR appears safe to merge because no blocking failure remains from the previously reported issues.

The synthetic test credential, profile redaction, endpoint coverage, alternate-list cache synchronization, and archive cache handling now address the prior findings without leaving a blocking residual failure.

Important Files Changed

Filename Overview
packages/toggl/client.test.ts Replaces the credential-shaped token fixture with a clearly synthetic value while retaining authentication and error-handler coverage.
packages/toggl/endpoints/me.ts Redacts profile credentials and synchronizes clients, projects, and tags returned through alternate /me collection routes.
packages/toggl/endpoints/clients.ts Synchronizes client mutations and safely refreshes or evicts cached clients after ID-envelope archive responses.
packages/toggl/endpoints/persist.ts Centralizes normalized cache writes and eviction behavior for the persisted Toggl entity types.
packages/toggl/endpoints.test.ts Adds direct request, normalization, and persistence assertions across the primary endpoint wrappers.
packages/toggl/endpoints-extended.test.ts Adds direct coverage for the extended operation surface, including the previously omitted tag wrapper and alternate cache-populating routes.
packages/toggl/endpoints/types.ts Models profile outputs without exposing the reusable Toggl API token.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Caller["Corsair caller"] --> Endpoint["Toggl endpoint wrapper"]
  Endpoint --> API["Toggl Track API"]
  API --> Redaction["Credential redaction"]
  Redaction --> Cache["Entity cache synchronization"]
  Cache --> Result["Safe endpoint result"]
Loading

Reviews (7): Last reviewed commit: "fix(toggl): use country_id for subdivisi..." | Re-trigger Greptile

Comment thread packages/toggl/client.test.ts Outdated
Comment thread packages/toggl/endpoints/types.ts Outdated
Comment thread packages/toggl/endpoints/workspaces.ts
Comment thread packages/toggl/endpoints/index.ts
@github-actions

Copy link
Copy Markdown

Plugin PR scorecard — packages/toggl

Check Status Notes
R1 — Scope: plugin files only
R2 — Tests with assertions
R3 — Description complete
R3 — Linked issue / claim
R4 — Demo video / recording

Rules: PLUGIN_PR_RULES.md · re-runs on every push

@github-actions

Copy link
Copy Markdown

Hey @Agam00, thanks for the contribution! 🏴‍☠️ Before a maintainer reviews, please fix the items below — the review re-runs automatically on your next push.

Must fix

  • P0 packages/toggl/client.test.ts:5Hardcoded credential-shaped token
    This test commits a fixed 32-character hexadecimal value as TOKEN, violating the repository-wide prohibition on hardcoded tokens. If this value is a real Toggl credential, anyone with source access can authenticate as that user; replace it with an unmistakably synthetic or generated fixture. How this was verified: The changed line directly declares the fixed credential-shaped string as the test token.

Rule Used: Flag any use of eval, new Function(), or execution... (source)

  • P1 packages/toggl/endpoints/types.ts:15Profile output exposes API token
    When a consumer calls me.get or me.update, Toggl returns api_token in the user profile and the handlers pass that response through unchanged, exposing a reusable, non-expiring account credential to ordinary endpoint consumers. Remove the credential from returned profile data rather than modeling it as public output. How this was verified: The captured live /me fixture contains api_token, while both profile handlers and the core binding return results without redaction.

Knowledge Base Used: The provider-plugin package pattern

  • P1 packages/toggl/endpoints/workspaces.ts:23Declared entity cache stays empty
    When callers read or mutate workspaces, clients, projects, or tags, these handlers only call Toggl, log, and return; they never synchronize the entities declared in TogglSchema. Because schema registration does not persist results automatically, plugin DB lookups remain empty or stale and cannot provide the advertised ID-resolution cache.

Knowledge Base Used: The provider-plugin package pattern

  • P1 packages/toggl/endpoints/index.ts:114Endpoint wrappers remain untested
    The added tests call makeTogglRequest directly or validate schema registries, but none invokes these 37 endpoint wrappers or the bound plugin API. Endpoint-specific paths, queries, bodies, normalization, and persistence behavior can therefore be incorrect while the suite passes, contrary to the repository requirement that every implemented endpoint have a corresponding test.

Rule Used: Flag any types on exported or public surfaces as... (source)

Knowledge Base Used: The provider-plugin package pattern

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

If anything remains after your next push, a bot commit will clean it up; a maintainer always does the final review and merge.

@github-actions github-actions Bot added the bot:round-1 Review bot posted consolidated findings label Aug 12, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (6)
packages/toggl/endpoints/types.ts (3)

458-459: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Align the comment with the optional field.

created_with is optional here. endpoints/time-entries.ts line 85 supplies the default 'corsair'. Update the comment to state that the endpoint fills the default.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/toggl/endpoints/types.ts` around lines 458 - 459, Update the JSDoc
comment for the created_with field in the relevant schema to indicate that it is
optional and the endpoint supplies the default value “corsair,” keeping the
z.string().optional() definition unchanged.

421-429: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Enforce the documented start_date/end_date pairing.

The comment states that start_date must be paired with end_date. The schema does not enforce this. Add a refinement so the mismatch fails locally instead of at the API.

♻️ Proposed change
-	meta: z.boolean().optional(),
-});
+	meta: z.boolean().optional(),
+}).refine(
+	(v) => (v.start_date === undefined) === (v.end_date === undefined),
+	{ error: 'start_date and end_date must be provided together' },
+);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/toggl/endpoints/types.ts` around lines 421 - 429, Update
TimeEntriesListInputSchema to add an object-level refinement requiring
start_date and end_date to either both be provided or both be absent. Ensure
validation rejects either mismatched combination locally while preserving all
existing field validation.

176-182: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Use z.email() for MeUpdateInputSchema.email.

The field currently accepts any string. Zod 4.4.3 supports z.email() and rejects invalid addresses during input validation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/toggl/endpoints/types.ts` around lines 176 - 182, Update the email
field in MeUpdateInputSchema to use Zod’s z.email() validator while preserving
its optional behavior, so invalid email addresses are rejected during input
validation.
packages/toggl/client.ts (2)

20-35: 🚀 Performance & Scalability | 🔵 Trivial

Consider proactive pacing in addition to retries.

Toggl allows roughly one request per second per token. This config reacts after a 429 only. With five retries and a 2x multiplier, one call can sleep about 31 seconds before it fails. integration.test.ts adds its own pace() helper, which shows the gap. A shared token-bucket limiter in front of request would smooth bursts and reduce 429 responses.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/toggl/client.ts` around lines 20 - 35, Add proactive token-bucket
pacing before requests are issued, reusing the shared rate-limiting path around
request rather than relying only on TOGGL_RATE_LIMIT_CONFIG retries. Configure
it for roughly one request per second per API token, ensure concurrent calls
share the limiter, and preserve the existing 429 retry and Retry-After handling.

8-16: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the unused TogglAPIError class.

No repository code constructs or throws TogglAPIError.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/toggl/client.ts` around lines 8 - 16, Remove the unused
TogglAPIError class from the client module, including its constructor and
related definition, while leaving the surrounding API client code unchanged.
packages/toggl/endpoints/clients.ts (1)

7-17: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

List handlers declare a non-nullable transport result but guard against null. Each list handler passes the plugin output type as the makeTogglRequest generic. That type excludes null, while Toggl returns null for an empty collection. The type checker therefore treats each ?? [] guard as dead code, and a later cleanup could remove it and reintroduce a null return. Add | null to the generic at each site.

  • packages/toggl/endpoints/clients.ts#L7-L17: change the generic to TogglEndpointOutputs['clientsList'] | null.
  • packages/toggl/endpoints/projects.ts#L7-L21: change the generic to TogglEndpointOutputs['projectsList'] | null.
  • packages/toggl/endpoints/tasks.ts#L7-L13: change the generic to TogglEndpointOutputs['tasksList'] | null.
  • packages/toggl/endpoints/tags.ts#L7-L13: change the generic to TogglEndpointOutputs['tagsList'] | null.
  • packages/toggl/endpoints/time-entries.ts#L7-L20: change the generic to TogglEndpointOutputs['timeEntriesList'] | null.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/toggl/endpoints/clients.ts` around lines 7 - 17, Update the
makeTogglRequest generic in the list handlers to include null, preserving each
existing null-to-empty-array fallback: packages/toggl/endpoints/clients.ts lines
7-17 use clientsList | null; packages/toggl/endpoints/projects.ts lines 7-21 use
projectsList | null; packages/toggl/endpoints/tasks.ts lines 7-13 use tasksList
| null; packages/toggl/endpoints/tags.ts lines 7-13 use tagsList | null; and
packages/toggl/endpoints/time-entries.ts lines 7-20 use timeEntriesList | null.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/toggl/endpoints/me.ts`:
- Line 36: Update the logEventFromContext call in the me update handler to
exclude profile values from the payload. Log only the update operation and the
names of fields being changed, without spreading input or persisting
email/fullname values.

In `@packages/toggl/endpoints/time-entries.ts`:
- Around line 89-128: Update the create and update handlers in
packages/toggl/endpoints/time-entries.ts (lines 89-128) so logEventFromContext
records only workspace_id, project_id, task_id, and the time-entry id, excluding
description and other free-text input fields. Also update the create and update
handlers in packages/toggl/endpoints/clients.ts (lines 28-80) to record only
workspace_id and client_id, excluding name; replace each full-input event
payload with these identifier-only fields.

In `@packages/toggl/error-handlers.ts`:
- Around line 38-71: Update AUTH_ERROR and PERMISSION_ERROR in
packages/toggl/error-handlers.ts (lines 38-71) to inspect ApiError.body for
Toggl’s invalid-token condition, classifying matching 403 responses as
authentication failures and excluding them from permission failures. Add a 403
ApiError invalid-token fixture and assertions in packages/toggl/client.test.ts
(lines 109-122) confirming it matches AUTH_ERROR but not PERMISSION_ERROR.

In `@packages/toggl/integration.test.ts`:
- Around line 75-100: Make remote-resource cleanup failure-safe across
packages/toggl/integration.test.ts: in lines 75-100, 103-126, and 129-151, track
each created client, project, and tag and delete it from finally or equivalent
cleanup even when later requests or assertions fail; in lines 154-196, stop and
delete the created time entry in the same failure-safe cleanup; update the
cleanup guarantee at line 13 to reflect this behavior.
- Around line 208-212: Update the unknown-resource test around makeTogglRequest
to use a guaranteed-unresolvable Toggl route instead of clients/1, then assert
the specific expected not-found error rather than only checking that some error
is thrown.

In `@packages/toggl/schema/database.ts`:
- Around line 28-34: Update the Toggl client persistence flow using
TogglClientEntity and db.clients.upsertByEntityId to map the API response’s wid
field into workspace_id before schema parsing or persistence, preserving the
workspace relation instead of allowing wid to be stripped as unknown.

---

Nitpick comments:
In `@packages/toggl/client.ts`:
- Around line 20-35: Add proactive token-bucket pacing before requests are
issued, reusing the shared rate-limiting path around request rather than relying
only on TOGGL_RATE_LIMIT_CONFIG retries. Configure it for roughly one request
per second per API token, ensure concurrent calls share the limiter, and
preserve the existing 429 retry and Retry-After handling.
- Around line 8-16: Remove the unused TogglAPIError class from the client
module, including its constructor and related definition, while leaving the
surrounding API client code unchanged.

In `@packages/toggl/endpoints/clients.ts`:
- Around line 7-17: Update the makeTogglRequest generic in the list handlers to
include null, preserving each existing null-to-empty-array fallback:
packages/toggl/endpoints/clients.ts lines 7-17 use clientsList | null;
packages/toggl/endpoints/projects.ts lines 7-21 use projectsList | null;
packages/toggl/endpoints/tasks.ts lines 7-13 use tasksList | null;
packages/toggl/endpoints/tags.ts lines 7-13 use tagsList | null; and
packages/toggl/endpoints/time-entries.ts lines 7-20 use timeEntriesList | null.

In `@packages/toggl/endpoints/types.ts`:
- Around line 458-459: Update the JSDoc comment for the created_with field in
the relevant schema to indicate that it is optional and the endpoint supplies
the default value “corsair,” keeping the z.string().optional() definition
unchanged.
- Around line 421-429: Update TimeEntriesListInputSchema to add an object-level
refinement requiring start_date and end_date to either both be provided or both
be absent. Ensure validation rejects either mismatched combination locally while
preserving all existing field validation.
- Around line 176-182: Update the email field in MeUpdateInputSchema to use
Zod’s z.email() validator while preserving its optional behavior, so invalid
email addresses are rejected during input validation.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ad5537d6-3231-4545-810b-0141d8643439

📥 Commits

Reviewing files that changed from the base of the PR and between bbb7cb9 and d562590.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (27)
  • packages/corsair/core/constants.ts
  • packages/toggl/client.test.ts
  • packages/toggl/client.ts
  • packages/toggl/endpoints/clients.ts
  • packages/toggl/endpoints/index.ts
  • packages/toggl/endpoints/me.ts
  • packages/toggl/endpoints/organizations.ts
  • packages/toggl/endpoints/projects.ts
  • packages/toggl/endpoints/tags.ts
  • packages/toggl/endpoints/tasks.ts
  • packages/toggl/endpoints/time-entries.ts
  • packages/toggl/endpoints/types.ts
  • packages/toggl/endpoints/workspaces.ts
  • packages/toggl/error-handlers.ts
  • packages/toggl/index.ts
  • packages/toggl/integration.test.ts
  • packages/toggl/jest.config.cjs
  • packages/toggl/package.json
  • packages/toggl/schema.test.ts
  • packages/toggl/schema/database.ts
  • packages/toggl/schema/index.ts
  • packages/toggl/tsconfig.json
  • packages/toggl/tsup.config.ts
  • packages/toggl/webhooks/index.ts
  • packages/toggl/webhooks/oauth-tenant-link.ts
  • packages/toggl/webhooks/tenant-matcher.ts
  • packages/toggl/webhooks/types.ts

Comment thread packages/toggl/endpoints/me.ts Outdated
Comment thread packages/toggl/endpoints/time-entries.ts
Comment thread packages/toggl/error-handlers.ts
Comment thread packages/toggl/integration.test.ts Outdated
Comment on lines +208 to +212
it('surfaces a clear error for an unknown resource', async () => {
await pace();
await expect(
makeTogglRequest<unknown>(`workspaces/${workspaceId}/clients/1`, token),
).rejects.toThrow();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use a guaranteed invalid route.

Client ID 1 can exist in the selected workspace. The request can then succeed and make this test fail. Use a route that Toggl cannot resolve, and assert the expected not-found error.

Proposed fix
-			makeTogglRequest<unknown>(`workspaces/${workspaceId}/clients/1`, token),
+			makeTogglRequest<unknown>('corsair-test-route-does-not-exist', token),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('surfaces a clear error for an unknown resource', async () => {
await pace();
await expect(
makeTogglRequest<unknown>(`workspaces/${workspaceId}/clients/1`, token),
).rejects.toThrow();
it('surfaces a clear error for an unknown resource', async () => {
await pace();
await expect(
makeTogglRequest<unknown>('corsair-test-route-does-not-exist', token),
).rejects.toThrow();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/toggl/integration.test.ts` around lines 208 - 212, Update the
unknown-resource test around makeTogglRequest to use a guaranteed-unresolvable
Toggl route instead of clients/1, then assert the specific expected not-found
error rather than only checking that some error is thrown.

Comment on lines +28 to +34
export const TogglClientEntity = z.object({
id: z.number(),
workspace_id: z.number().nullable().optional(),
name: z.string(),
archived: z.boolean().nullable().optional(),
at: z.coerce.date().nullable().optional(),
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the client persistence path and verify that it maps Toggl's `wid`.
ast-grep outline packages/toggl --items all --type function
rg -n -C 8 '\bTogglClientEntity\b|\bwid\b|\bworkspace_id\b' packages/toggl

Repository: corsairdev/corsair

Length of output: 50374


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- Toggl package files ---'
git ls-files packages/toggl

printf '%s\n' '--- Toggl schema and persistence-related references ---'
rg -n -C 5 'TogglSchema|entities:|database|persist|parse\(|safeParse\(|clientsList|clientsGet' packages/toggl packages/corsair/core --glob '*.{ts,tsx,js,jsx}' | head -n 500

printf '%s\n' '--- Relevant source sections ---'
sed -n '1,180p' packages/toggl/endpoints/clients.ts
sed -n '1,130p' packages/toggl/schema/index.ts
sed -n '1,90p' packages/toggl/schema/database.ts
sed -n '80,110p' packages/toggl/endpoints/types.ts

Repository: corsairdev/corsair

Length of output: 39421


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- Entity client construction and schema usage ---'
rg -n -C 8 'createKyselyEntityClient|PluginEntityClient|schema\.entities|entities\[' packages/corsair/core packages/corsair/db --glob '*.{ts,tsx}' | head -n 600

printf '%s\n' '--- Complete relevant core client sections ---'
sed -n '1,180p' packages/corsair/core/client/index.ts
sed -n '1,260p' packages/corsair/db/kysely/orm.ts

printf '%s\n' '--- All Toggl entity-client references ---'
rg -n -C 8 'entities\.(workspaces|clients|projects|tags)|\.clients\b|TogglClientEntity' packages --glob '*.{ts,tsx}' | head -n 500

Repository: corsairdev/corsair

Length of output: 37766


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- Entity write methods ---'
sed -n '239,560p' packages/corsair/db/kysely/orm.ts

printf '%s\n' '--- Entity client write type definitions ---'
sed -n '1090,1215p' packages/corsair/db/orm.ts

printf '%s\n' '--- Entity write call sites and Toggl database usage ---'
rg -n -C 6 '\.(create|upsert|update)\s*\(' packages --glob '*.{ts,tsx}' | rg -C 4 'db|entity|Toggl|clients|projects|tags|workspaces' | head -n 500

Repository: corsairdev/corsair

Length of output: 24847


🏁 Script executed:

#!/bin/bash
set -euo pipefail

node - <<'JS'
const zodPath = require.resolve('zod');
const { z } = require(zodPath);

const TogglClientEntity = z.object({
  id: z.number(),
  workspace_id: z.number().nullable().optional(),
  name: z.string(),
  archived: z.boolean().nullable().optional(),
  at: z.coerce.date().nullable().optional(),
});

const input = {
  id: 69027594,
  wid: 21597802,
  name: 'Acme Corp',
  archived: false,
  at: '2026-08-12T09:50:41+00:00',
};

const parsed = TogglClientEntity.parse(input);
console.log(JSON.stringify({
  zodPath,
  parsedKeys: Object.keys(parsed),
  parsed,
  widPreserved: Object.prototype.hasOwnProperty.call(parsed, 'wid'),
  workspaceId: parsed.workspace_id ?? null,
}));
JS

Repository: corsairdev/corsair

Length of output: 944


🌐 Web query:

Zod official documentation object schemas unknown keys default strip behavior

💡 Result:

In Zod, the default behavior for object schemas when encountering unknown keys (keys not explicitly defined in the schema) is to strip them from the parsed result [1][2][3]. This means if you parse an input object containing extra keys, Zod will return a new object containing only the keys specified in your schema [1][3]. Example of default behavior: const User = z.object({ name: z.string }); User.parse({ name: "Alice", age: 30 }); // Result: { name: "Alice" } To change this behavior, you can use the following methods on your object schema:.strict Disallows unknown keys by throwing a ZodError if any are present during parsing [2][3][4]..passthrough (or.loose in newer versions) Allows unknown keys to pass through the parsed result without modification or validation [2][3][4]..catchall(schema) Validates all unknown keys against a provided Zod schema [5][4]. When.catchall is used, it takes precedence over.strip,.strict, or.passthrough, and all keys are considered "known" [2][6][7].

Citations:


Map wid to workspace_id before persistence.

db.clients.upsertByEntityId parses data with TogglClientEntity, which strips the API response’s unknown wid field. Directly persisting a TogglClient therefore loses the workspace relation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/toggl/schema/database.ts` around lines 28 - 34, Update the Toggl
client persistence flow using TogglClientEntity and db.clients.upsertByEntityId
to map the API response’s wid field into workspace_id before schema parsing or
persistence, preserving the workspace relation instead of allowing wid to be
stripped as unknown.

@Agam00
Agam00 force-pushed the feat/toggl-plugin branch from d562590 to 853ba2b Compare August 12, 2026 10:56
@Agam00

Agam00 commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

@greptile review

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/toggl/endpoints/clients.ts`:
- Around line 73-80: The clients update flow must separate detail updates from
archive state changes: in update, send only the client details through the PUT
request, then call the documented POST archive or restore operation when
input.archived requests a state change. Update the tests in
packages/toggl/endpoints.test.ts lines 246-256 to cover both archive and restore
routes rather than asserting archived in the PUT body.

In `@packages/toggl/endpoints/projects.ts`:
- Around line 8-20: Update TagsListInputSchema and Tags.list in
packages/toggl/endpoints/tags.ts to accept and forward page, per_page, and
search as query parameters alongside workspace_id. Add or update generated-query
coverage in packages/toggl/endpoints.test.ts at lines 271-279 and 393-403 to
verify all three parameters are included; packages/toggl/endpoints/projects.ts
lines 8-20 is the reference pattern and requires no direct change.

In `@packages/toggl/endpoints/types.ts`:
- Around line 447-455: Update TimeEntriesCreateInputSchema and the corresponding
update schema in packages/toggl/endpoints/types.ts at lines 447-455 and 470-476:
validate start and optional stop with z.iso.datetime(), and require duration to
be an integer via .int() in both mutation schemas.
- Around line 426-434: Update TimeEntriesListInputSchema so start_date,
end_date, and before validate ISO date or RFC3339 datetime strings with offsets
via the appropriate z.iso validators, while preserving their optional status. Do
not add cross-field validation requiring start_date and end_date to be supplied
together.

In `@packages/toggl/endpoints/workspaces.ts`:
- Around line 33-49: Remove api_token from workspace response objects by
introducing one shared sanitizer and applying it in workspaces.get,
workspaces.list, and workspaces.update before returning results. Preserve all
other workspace fields and add regression coverage for responses containing
api_token.

In `@packages/toggl/integration.test.ts`:
- Around line 80-85: Update the integration test setup around beforeAll and the
authenticated-user test so the first makeTogglRequest('me', token) call is
delayed by the declared one-second request margin when workspace discovery has
sent the workspaces request. Preserve existing timing behavior when
TOGGL_WORKSPACE_ID is already provided.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f2b9038e-1be5-460a-8295-17bd04ffed80

📥 Commits

Reviewing files that changed from the base of the PR and between d562590 and 853ba2b.

📒 Files selected for processing (16)
  • packages/toggl/client.test.ts
  • packages/toggl/endpoints.test.ts
  • packages/toggl/endpoints/clients.ts
  • packages/toggl/endpoints/logging.ts
  • packages/toggl/endpoints/me.ts
  • packages/toggl/endpoints/organizations.ts
  • packages/toggl/endpoints/persist.ts
  • packages/toggl/endpoints/projects.ts
  • packages/toggl/endpoints/tags.ts
  • packages/toggl/endpoints/tasks.ts
  • packages/toggl/endpoints/time-entries.ts
  • packages/toggl/endpoints/types.ts
  • packages/toggl/endpoints/workspaces.ts
  • packages/toggl/error-handlers.ts
  • packages/toggl/integration.test.ts
  • packages/toggl/schema.test.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/toggl/endpoints/organizations.ts
  • packages/toggl/client.test.ts
  • packages/toggl/schema.test.ts
  • packages/toggl/endpoints/tasks.ts
  • packages/toggl/endpoints/time-entries.ts
  • packages/toggl/error-handlers.ts

Comment thread packages/toggl/endpoints/clients.ts
Comment on lines +8 to +20
export const list: TogglEndpoints['projectsList'] = async (ctx, input) => {
const result = await makeTogglRequest<TogglEndpointOutputs['projectsList']>(
`workspaces/${input.workspace_id}/projects`,
ctx.key,
{
method: 'GET',
query: {
active: input.active,
name: input.name,
page: input.page,
per_page: input.per_page,
},
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- relevant files ---'
git ls-files 'packages/toggl/**' '.github/PLUGIN_PR_RULES.md' | sed -n '1,160p'
printf '%s\n' '--- endpoint outlines ---'
ast-grep outline packages/toggl/endpoints/projects.ts
ast-grep outline packages/toggl/endpoints/tags.ts
printf '%s\n' '--- project endpoint ---'
cat -n packages/toggl/endpoints/projects.ts
printf '%s\n' '--- tag endpoint ---'
cat -n packages/toggl/endpoints/tags.ts
printf '%s\n' '--- shared request and schemas ---'
rg -n -C 3 'makeTogglRequest|projectsList|tagsList|start_project_id|per_page|search' packages/toggl
printf '%s\n' '--- relevant tests ---'
sed -n '220,310p' packages/toggl/endpoints.test.ts
sed -n '360,425p' packages/toggl/endpoints.test.ts

Repository: corsairdev/corsair

Length of output: 50374


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- endpoint types: project and tag schemas ---'
sed -n '270,330p' packages/toggl/endpoints/types.ts
rg -n -A 18 -B 5 'TagsListInputSchema|ProjectsListOutput|TagsListOutput|ProjectsListInputSchema|TagsListInput' packages/toggl/endpoints/types.ts
printf '%s\n' '--- request implementation ---'
sed -n '1,135p' packages/toggl/client.ts
printf '%s\n' '--- focused endpoint tests ---'
sed -n '250,290p' packages/toggl/endpoints.test.ts
sed -n '380,415p' packages/toggl/endpoints.test.ts
printf '%s\n' '--- git state ---'
git diff --stat
git status --short
printf '%s\n' '--- project/tag references outside focused files ---'
rg -n -g '*.ts' 'start_project_id|projectsList|tagsList|/paginated|page:|per_page:|search:' packages/toggl | sed -n '1,220p'
printf '%s\n' '--- query serialization verifier ---'
python3 - <<'PY'
from urllib.parse import urlencode, urljoin

queries = [
    {"active": True, "name": None, "page": 2, "per_page": 50},
    {"active": None, "name": None, "page": None, "per_page": None},
]
for query in queries:
    # Model URLSearchParams-style filtering used by typical request serializers:
    filtered = {k: v for k, v in query.items() if v is not None}
    print(urlencode(filtered))
PY

Repository: corsairdev/corsair

Length of output: 14256


🌐 Web query:

Toggl Track API v9 projects list paginated start_project_id page per_page official documentation

💡 Result:

In the Toggl Track API v9, project listing is handled differently depending on whether you use standard list endpoints or specifically designed paginated endpoints. For user-specific projects, the API provides a dedicated paginated endpoint [1][2]: Endpoint: GET /api/v9/me/projects/paginated [2] Supported query parameters for this endpoint include [1][2]: - start_project_id: (integer, optional) The project ID to use as an offset to resume pagination [1][2]. - per_page: (integer, optional) The number of items to return per page (default is 201) [1][2]. - since: (integer, optional) Retrieves projects created, modified, or deleted since this Unix timestamp [1][2]. For workspace-level projects, the standard endpoint is typically: Endpoint: GET /api/v9/workspaces/{workspace_id}/projects [3][4] The workspace projects endpoint uses a page-based system rather than a project ID offset [3][4]: - page: (integer, required) The page number to retrieve [3][4]. - per_page: (integer, optional) The number of items per page (default is 151, maximum 200) [3][4]. Note that for some general project list requests, you can also append /paginated to a base URL to enable pagination features [5][6]. Always refer to the specific documentation for the resource you are querying, as parameter requirements (such as "required" versus "optional" status) can vary between the workspace and user-level endpoints [1][3].

Citations:


🌐 Web query:

Toggl Track API v9 workspace tags list page per_page search official documentation

💡 Result:

The Toggl Track API v9 endpoint for listing tags in a workspace supports pagination and search through standard query parameters. Endpoint: GET https://api.track.toggl.com/api/v9/workspaces/{workspace_id}/tags [1][2] Query Parameters: - page (integer, optional): Specifies the page number to retrieve [1][3]. - per_page (integer, optional): Specifies the number of items to return per page [1][3]. - search (string, optional): Allows filtering/searching the tags by name [1][3]. Example Request: To retrieve tags for a specific workspace with pagination and a search filter, you can append the parameters to the URL: curl -X GET "https://api.track.toggl.com/api/v9/workspaces/{workspace_id}/tags?page=1&per_page=50&search=example" \ -H "Accept: application/json" [3] This endpoint returns a 200 OK response with an array of tag objects [1][3]. Ensure that the {workspace_id} is replaced with the numeric ID of the target workspace [1][4].

Citations:


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- shared query serialization sources ---'
rg -n -C 5 'URLSearchParams|query.*Object|Object\.entries\(.*query|query:' packages --glob '*.{ts,tsx,js,jsx}' | sed -n '1,240p'
printf '%s\n' '--- tag list conventions in repository ---'
rg -n -C 5 'search: input\.|page: input\.|per_page: input\.|auditPayload\(input.*tags|tags\.list' packages --glob '*.{ts,tsx}' | sed -n '1,240p'
printf '%s\n' '--- source-backed contract verifier ---'
python3 - <<'PY'
from pathlib import Path
import re
from urllib.parse import urlencode

types = Path("packages/toggl/endpoints/types.ts").read_text()
tags = Path("packages/toggl/endpoints/tags.ts").read_text()
projects = Path("packages/toggl/endpoints/projects.ts").read_text()

tag_schema = re.search(
    r"const TagsListInputSchema = z\.object\(\{(?P<body>.*?)\n\}\);",
    types,
    re.S,
)
assert tag_schema, "TagsListInputSchema not found"
tag_fields = set(re.findall(r"^\s*([A-Za-z_][A-Za-z0-9_]*)\s*:", tag_schema.group("body"), re.M))
print("tag_input_fields:", sorted(tag_fields))
print("tag_query_present:", "query:" in tags)
print("project_workspace_route:", re.search(
    r"`workspaces/\$\{input\.workspace_id\}/projects`", projects
) is not None)
print("project_uses_start_project_id:", "start_project_id" in projects)
assert tag_fields == {"workspace_id"}
assert "query:" not in tags
assert "start_project_id" not in projects

params = {"page": 2, "per_page": 50, "search": "billable"}
print("expected_tag_query:", urlencode(params))
PY

Repository: corsairdev/corsair

Length of output: 28744


🌐 Web query:

Toggl Track official API v9 workspace projects endpoint page per_page not paginated start_project_id

💡 Result:

In the Toggl Track API v9, the standard workspace projects endpoint (GET /workspaces/{workspace_id}/projects) does not support cursor-based pagination using start_project_id, nor does it typically use page-based parameters like per_page in the same manner as paginated endpoints [1][2]. For scenarios requiring pagination, Toggl Track provides dedicated paginated endpoints. Key distinctions are outlined below: 1. Standard Workspace Projects Endpoint: The endpoint GET /api/v9/workspaces/{workspace_id}/projects returns a list of projects for a given workspace [1][2]. While official documentation for this specific endpoint lists parameters like page and per_page (with a default of 151 and a maximum of 200) [3][2], it does not support the start_project_id parameter [3][2]. 2. Paginated Endpoints (e.g., /me/projects/paginated): When you require more advanced pagination, such as cursor-based navigation, you should use endpoints that specifically include /paginated in the path (e.g., GET /api/v9/me/projects/paginated) [1][4]. These endpoints explicitly support: - start_project_id: A query parameter used to resume pagination from a specific project ID [4][5]. - per_page: A query parameter to define the number of items per page (default is 201 for these endpoints) [4][6]. If you are working with workspace-level projects and require cursor-based pagination, ensure you are utilizing an endpoint designed for pagination, as the base workspace projects endpoint is not structured for this advanced pagination method [1][7].

Citations:


Forward pagination and search parameters for tag listing.

TagsListInputSchema accepts only workspace_id, and Tags.list sends no query parameters. Add page, per_page, and search to the input schema, forward them, and test the generated query.

📍 Affects 3 files
  • packages/toggl/endpoints/projects.ts#L8-L20 (this comment)
  • packages/toggl/endpoints/tags.ts#L8-L13
  • packages/toggl/endpoints.test.ts#L271-L279
  • packages/toggl/endpoints.test.ts#L393-L403
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/toggl/endpoints/projects.ts` around lines 8 - 20, Update
TagsListInputSchema and Tags.list in packages/toggl/endpoints/tags.ts to accept
and forward page, per_page, and search as query parameters alongside
workspace_id. Add or update generated-query coverage in
packages/toggl/endpoints.test.ts at lines 271-279 and 393-403 to verify all
three parameters are included; packages/toggl/endpoints/projects.ts lines 8-20
is the reference pattern and requires no direct change.

Comment thread packages/toggl/endpoints/types.ts Outdated
Comment thread packages/toggl/endpoints/types.ts Outdated
Comment on lines +33 to +49
export const get: TogglEndpoints['workspacesGet'] = async (ctx, input) => {
const result = await makeTogglRequest<TogglEndpointOutputs['workspacesGet']>(
`workspaces/${input.workspace_id}`,
ctx.key,
{ method: 'GET' },
);

await cacheWorkspace(ctx.db.workspaces, result);

await logEventFromContext(
ctx,
'toggl.workspaces.get',
auditPayload(input, ['workspace_id']),
'completed',
);
return result;
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- target file ---'
cat -n packages/toggl/endpoints/workspaces.ts

printf '%s\n' '--- sanitizer and related workspace handlers ---'
rg -n -C 5 'cacheWorkspace|api_token|workspacesGet|Me\.get|workspaces' packages/toggl

printf '%s\n' '--- package files and tests ---'
git ls-files packages/toggl | sed -n '1,200p'

Repository: corsairdev/corsair

Length of output: 50374


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- workspace schemas and endpoint output types ---'
cat -n packages/toggl/schema/api.ts 2>/dev/null || true
cat -n packages/toggl/endpoints/types.ts
rg -n -C 8 'TogglWorkspaceSchema|workspacesGet|workspacesList|TogglEndpointOutputSchemas' packages/toggl/schema packages/toggl/endpoints packages/toggl/index.ts

printf '%s\n' '--- me implementation ---'
fd -i 'me*.ts' packages/toggl --exec sh -c 'echo "--- $1"; cat -n "$1"' sh {}

printf '%s\n' '--- workspace persistence ---'
cat -n packages/toggl/endpoints/persist.ts

printf '%s\n' '--- focused endpoint test setup and workspace tests ---'
sed -n '1,190p' packages/toggl/endpoints.test.ts

Repository: corsairdev/corsair

Length of output: 50374


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- me endpoint implementation ---'
rg -l 'api_token|export const get.*Me|meGet' packages/toggl/endpoints --glob '*.ts' | while read -r f; do
  echo "--- $f"
  cat -n "$f"
done

printf '%s\n' '--- focused workspace test section ---'
sed -n '1,190p' packages/toggl/endpoints.test.ts

printf '%s\n' '--- output-schema validation and endpoint invocation ---'
rg -n -C 6 'output\.parse|output\.safeParse|OutputSchemas|endpointSchemas|schema\.parse|safeParse' packages/corsair packages --glob '*.{ts,tsx}' \
  | rg -v 'node_modules|dist|build' | sed -n '1,260p'

Repository: corsairdev/corsair

Length of output: 50374


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- core endpoint machinery ---'
git ls-files packages/corsair/core | sed -n '1,200p'
rg -n -C 8 'endpointSchemas|outputSchema|\.output|safeParse|parse\(result|parse\(response' packages/corsair/core packages/corsair --glob '*.{ts,tsx}' \
  | sed -n '1,320p'

printf '%s\n' '--- plugin endpoint binding and direct exports ---'
rg -n -C 6 'bind.*Endpoint|endpointsNested|plugin\.endpoints|endpointSchemas' packages/corsair packages/toggl --glob '*.{ts,tsx}' \
  | sed -n '1,320p'

Repository: corsairdev/corsair

Length of output: 41243


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- endpoint binding implementation ---'
sed -n '1,380p' packages/corsair/core/endpoints/bind.ts

printf '%s\n' '--- plugin client endpoint context and returned endpoint tree ---'
sed -n '460,555p' packages/corsair/core/client/index.ts

Repository: corsairdev/corsair

Length of output: 14683


🌐 Web query:

Toggl Track API v9 workspaces response api_token

💡 Result:

In the Toggl Track API v9, the api_token field is included in the response for workspace-related endpoints, but it is explicitly marked as deprecated [1][2][3][4][5]. As it is a deprecated field, it should not be relied upon for current or future integrations [1][5].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- Toggl request response handling ---'
sed -n '1,180p' packages/toggl/client.ts

printf '%s\n' '--- read-only source invariant check ---'
python3 - <<'PY'
from pathlib import Path

workspace = Path("packages/toggl/endpoints/workspaces.ts").read_text()
binding = Path("packages/corsair/core/endpoints/bind.ts").read_text()
client = Path("packages/toggl/client.ts").read_text()

checks = {
    "workspace get returns raw result": "return result;" in workspace[workspace.index("export const get:"):workspace.index("export const update:")],
    "workspace get has no credential sanitizer": "withoutCredentials" not in workspace[workspace.index("export const get:"):workspace.index("export const update:")],
    "request client returns parsed provider body": "return (await response.json()) as T;" in client or "return (await response.json())" in client,
    "bound endpoint returns handler result without output parsing": "return res;" in binding and "endpointSchemas" not in binding,
}
for name, passed in checks.items():
    print(f"{name}: {'PASS' if passed else 'FAIL'}")
if not all(checks.values()):
    raise SystemExit(1)
PY

Repository: corsairdev/corsair

Length of output: 2609


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- HTTP request implementation ---'
git ls-files packages/corsair/http | sed -n '1,160p'
rg -n -C 10 'export.*request|async function request|response\.json|return.*response|parse' packages/corsair/http --glob '*.{ts,tsx}' \
  | sed -n '1,260p'

printf '%s\n' '--- direct endpoint exposure tests ---'
rg -n -C 8 'bindEndpointsRecursively|endpointSchemas|Workspaces\.get|workspaces\.get' packages/corsair packages/toggl --glob '*.{test,spec}.{ts,tsx}' \
  | sed -n '1,260p'

Repository: corsairdev/corsair

Length of output: 5079


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- locate corsair/http implementation ---'
git ls-files | rg '(^|/)(http|request)(/|\.|$)' | sed -n '1,240p'
rg -n -C 8 'export (async )?function request|function request\(|response\.json\(\)|ApiRequestOptions' . \
  --glob '*.{ts,tsx}' \
  --glob '!**/node_modules/**' \
  --glob '!**/dist/**' \
  --glob '!**/build/**' \
  | sed -n '1,320p'

Repository: corsairdev/corsair

Length of output: 18906


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- packages/corsair/http.ts ---'
cat -n packages/corsair/http.ts

Repository: corsairdev/corsair

Length of output: 1458


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- shared request implementation ---'
cat -n packages/corsair/async-core/request.ts

Repository: corsairdev/corsair

Length of output: 13797


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- workspace cache implementation ---'
sed -n '1,110p' packages/toggl/endpoints/persist.ts

printf '%s\n' '--- entity-store persistence behavior ---'
rg -n -C 8 'upsertByEntityId|class.*Entity|EntityStore|JSON\.stringify|workspace' packages/corsair packages/toggl --glob '*.{ts,tsx}' \
  | rg -v 'endpoints\.test|integration\.test|schema\.test' \
  | sed -n '1,320p'

Repository: corsairdev/corsair

Length of output: 23384


Remove api_token from every workspace response.

The shared request client returns response.body unchanged, and endpoint binding does not apply TogglEndpointOutputSchemas. Apply one sanitizer to workspaces.get, workspaces.list, and workspaces.update. Add regression tests with api_token.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/toggl/endpoints/workspaces.ts` around lines 33 - 49, Remove
api_token from workspace response objects by introducing one shared sanitizer
and applying it in workspaces.get, workspaces.list, and workspaces.update before
returning results. Preserve all other workspace fields and add regression
coverage for responses containing api_token.

Comment thread packages/toggl/integration.test.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/toggl/endpoints/smail.ts`:
- Around line 68-72: Update the auditPayload call in the toggl.smail.sendMeet
completion logging to pass an empty identifier list instead of ['location'], so
the audit event records supplied field names without persisting the meeting
location.

In `@packages/toggl/endpoints/types.ts`:
- Around line 817-826: Update the operations schema to use a discriminated union
on op: keep remove as its own variant without value, and require value for add
and replace variants. Preserve the existing non-empty array constraint and path
validation.
- Around line 181-185: Update TogglQuotaSchema.organization_id to accept null
values by making its numeric schema nullable, while preserving the existing
required field and all other quota fields unchanged.
- Around line 697-729: Define and reuse a shared TogglIdSchema based on an
integer number, then replace every resource-ID z.number() input throughout the
types with it, including optional or nullable IDs and tag_ids/time_entry_ids
array elements. Update the affected schemas such as
OrganizationsGetGroupsInputSchema, OrganizationsCreateGroupInputSchema, and
OrganizationsDeleteGroupInputSchema while leaving non-ID numeric fields
unchanged.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6c2b5d3f-344a-4ed5-9cd0-dcb2dadc8f2b

📥 Commits

Reviewing files that changed from the base of the PR and between 853ba2b and e2f8b74.

📒 Files selected for processing (17)
  • packages/toggl/client.test.ts
  • packages/toggl/client.ts
  • packages/toggl/endpoints-extended.test.ts
  • packages/toggl/endpoints.test.ts
  • packages/toggl/endpoints/index.ts
  • packages/toggl/endpoints/me.ts
  • packages/toggl/endpoints/organizations.ts
  • packages/toggl/endpoints/projects.ts
  • packages/toggl/endpoints/reference.ts
  • packages/toggl/endpoints/smail.ts
  • packages/toggl/endpoints/tasks.ts
  • packages/toggl/endpoints/time-entries.ts
  • packages/toggl/endpoints/types.ts
  • packages/toggl/endpoints/webhook-subscriptions.ts
  • packages/toggl/endpoints/workspaces.ts
  • packages/toggl/index.ts
  • packages/toggl/schema.test.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/toggl/endpoints.test.ts
  • packages/toggl/schema.test.ts
  • packages/toggl/client.ts
  • packages/toggl/client.test.ts

Comment thread packages/toggl/endpoints/smail.ts
Comment thread packages/toggl/endpoints/types.ts Outdated
Comment thread packages/toggl/endpoints/types.ts
Comment thread packages/toggl/endpoints/types.ts
@Agam00

Agam00 commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

@greptile review

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/toggl/endpoints/clients.ts`:
- Around line 119-125: Update the archive flow around the clientsArchive request
to define and use a response schema matching { items: number[] }, then remove
the cacheClient call because the archive response is not a TogglClient. If the
cache must reflect the archived state, fetch the client separately before
caching it with cacheClient.
- Line 58: Update the create payload in the relevant client creation method to
remove the wid field and input.workspace_id mapping. Preserve only the
documented body fields: external_reference, name, and notes.

In `@packages/toggl/endpoints/tasks.ts`:
- Around line 21-25: Branch the task query construction by route: when
project_id is set, send only the supported active parameter and omit page and
per_page; retain pagination parameters for the non-project task endpoint.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 236e6664-6647-4253-8e09-889ace078ce0

📥 Commits

Reviewing files that changed from the base of the PR and between e2f8b74 and e276edc.

📒 Files selected for processing (12)
  • packages/toggl/client.test.ts
  • packages/toggl/client.ts
  • packages/toggl/endpoints-extended.test.ts
  • packages/toggl/endpoints.test.ts
  • packages/toggl/endpoints/clients.ts
  • packages/toggl/endpoints/index.ts
  • packages/toggl/endpoints/tags.ts
  • packages/toggl/endpoints/tasks.ts
  • packages/toggl/endpoints/types.ts
  • packages/toggl/error-handlers.ts
  • packages/toggl/index.ts
  • packages/toggl/schema.test.ts
💤 Files with no reviewable changes (1)
  • packages/toggl/client.ts
🚧 Files skipped from review as they are similar to previous changes (8)
  • packages/toggl/endpoints/tags.ts
  • packages/toggl/error-handlers.ts
  • packages/toggl/endpoints/index.ts
  • packages/toggl/client.test.ts
  • packages/toggl/index.ts
  • packages/toggl/endpoints-extended.test.ts
  • packages/toggl/endpoints/types.ts
  • packages/toggl/endpoints.test.ts

Comment thread packages/toggl/endpoints/clients.ts Outdated
Comment thread packages/toggl/endpoints/clients.ts Outdated
Comment thread packages/toggl/endpoints/tasks.ts Outdated
Comment thread packages/toggl/endpoints-extended.test.ts
@github-actions github-actions Bot added the bot:round-2 Review bot pushed an automated fix label Aug 12, 2026
@github-actions

Copy link
Copy Markdown

Remaining findings are being fixed by a bot commit — it will be re-reviewed automatically.

@Agam00

Agam00 commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

@greptile review

Comment thread packages/toggl/endpoints/me.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/toggl/endpoints/persist.ts (1)

1-35: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Keep the best-effort cache contract and add rejection-path tests.

The cache is a non-authoritative mirror, so provider calls may succeed when upsertByEntityId or deleteByEntityId fails. Add tests that assert failures are swallowed, warnings are emitted, and deleted records can remain stale.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/toggl/endpoints/persist.ts` around lines 1 - 35, Preserve the
best-effort behavior implemented by safely: cache failures from upsertByEntityId
and deleteByEntityId must be swallowed so provider calls still succeed, with a
warning emitted for each rejection. Add rejection-path tests for both
operations, including that failed deletion leaves the existing cached record
stale.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/toggl/schema.test.ts`:
- Around line 250-258: Update the timeEntriesCreate.parse fixture in the
“rejects a non-RFC3339 time entry start” test to use a valid duration value,
keeping the intentionally invalid start unchanged so the assertion specifically
exercises timestamp validation.

---

Nitpick comments:
In `@packages/toggl/endpoints/persist.ts`:
- Around line 1-35: Preserve the best-effort behavior implemented by safely:
cache failures from upsertByEntityId and deleteByEntityId must be swallowed so
provider calls still succeed, with a warning emitted for each rejection. Add
rejection-path tests for both operations, including that failed deletion leaves
the existing cached record stale.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5fbf600c-50a6-4d58-b853-8f98853f372b

📥 Commits

Reviewing files that changed from the base of the PR and between e276edc and 305dbb6.

📒 Files selected for processing (20)
  • packages/toggl/client.test.ts
  • packages/toggl/client.ts
  • packages/toggl/endpoints-extended.test.ts
  • packages/toggl/endpoints.test.ts
  • packages/toggl/endpoints/clients.ts
  • packages/toggl/endpoints/me.ts
  • packages/toggl/endpoints/organizations.ts
  • packages/toggl/endpoints/persist.ts
  • packages/toggl/endpoints/projects.ts
  • packages/toggl/endpoints/reference.ts
  • packages/toggl/endpoints/smail.ts
  • packages/toggl/endpoints/tags.ts
  • packages/toggl/endpoints/tasks.ts
  • packages/toggl/endpoints/time-entries.ts
  • packages/toggl/endpoints/types.ts
  • packages/toggl/endpoints/webhook-subscriptions.ts
  • packages/toggl/endpoints/workspaces.ts
  • packages/toggl/index.ts
  • packages/toggl/integration.test.ts
  • packages/toggl/schema.test.ts
🚧 Files skipped from review as they are similar to previous changes (18)
  • packages/toggl/endpoints/projects.ts
  • packages/toggl/client.test.ts
  • packages/toggl/endpoints/smail.ts
  • packages/toggl/endpoints.test.ts
  • packages/toggl/endpoints/tasks.ts
  • packages/toggl/endpoints/clients.ts
  • packages/toggl/endpoints/me.ts
  • packages/toggl/endpoints-extended.test.ts
  • packages/toggl/integration.test.ts
  • packages/toggl/client.ts
  • packages/toggl/endpoints/tags.ts
  • packages/toggl/endpoints/webhook-subscriptions.ts
  • packages/toggl/endpoints/reference.ts
  • packages/toggl/endpoints/workspaces.ts
  • packages/toggl/index.ts
  • packages/toggl/endpoints/time-entries.ts
  • packages/toggl/endpoints/organizations.ts
  • packages/toggl/endpoints/types.ts

Comment thread packages/toggl/schema.test.ts
@Agam00

Agam00 commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

@greptile review

Comment thread packages/toggl/endpoints/clients.ts Outdated
@github-actions

github-actions Bot commented Aug 12, 2026

Copy link
Copy Markdown

Maintainer review needed

Automated rounds are exhausted. Remaining findings:

  • P1 packages/toggl/endpoints/clients.tsArchive leaves cached client stale
    When Toggl successfully archives a cached client but returns its documented { items: [...] } envelope, this branch skips both refreshing and evicting the cache entry, causing subsequent local ID lookups to continue returning the client as unarchived.

Knowledge Base Used: The provider-plugin package pattern

@github-actions github-actions Bot added the needs-maintainer Automated rounds exhausted - human review needed label Aug 12, 2026
@Agam00

Agam00 commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

@greptile review

@Dhirenderchoudhary

Copy link
Copy Markdown
Collaborator

Fixed a small bug
@greptile review

@Dhirenderchoudhary

Copy link
Copy Markdown
Collaborator

LGTM tested locally API
Thanks for the contribution.

@devjain32
devjain32 merged commit 114acab into corsairdev:main Aug 12, 2026
10 of 11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bot:round-1 Review bot posted consolidated findings bot:round-2 Review bot pushed an automated fix core Changes in packages/corsair needs-maintainer Automated rounds exhausted - human review needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Integration request: Toggl Track

3 participants