Skip to content

feat(gmail): Google Workspace SDK integration — auto-detect interview invites from Gmail#663

Open
tauheedbuttt wants to merge 4 commits into
santifer:mainfrom
tauheedbuttt:feat/scan-gmail-workspace-sdk
Open

feat(gmail): Google Workspace SDK integration — auto-detect interview invites from Gmail#663
tauheedbuttt wants to merge 4 commits into
santifer:mainfrom
tauheedbuttt:feat/scan-gmail-workspace-sdk

Conversation

@tauheedbuttt
Copy link
Copy Markdown

@tauheedbuttt tauheedbuttt commented May 16, 2026

Summary

Implements the full feature request from #660.

  • Adds scan-gmail.mjs: zero-LLM Gmail scanner using Google's official OAuth2 SDK
  • Queries Gmail for interview invitation emails matching configurable keyword subjects
  • Extracts company, role, interview date/time, meeting link, and interviewer from each email
  • Deduplicates against existing applications.md to avoid double-tracking
  • Writes TSV tracker additions (status: Interview) following the merge-tracker pattern
  • Creates interview-prep/{company}-{role}.md stub files for each new interview
  • Adds Google Calendar events via Calendar API (skippable with --no-calendar)
  • OAuth2 flow with local callback server on port 3000, token persisted to calendar/token.json
  • calendar/credentials.json and calendar/token.json added to .gitignore
  • New google: section in config/profile.example.yml for scan days and calendar ID
  • README section: "Gmail Interview Scanner" with setup steps
  • package.json script: npm run scan:gmail

Usage

node scan-gmail.mjs            # scan last 7 days
node scan-gmail.mjs --days 14  # scan last N days
node scan-gmail.mjs --dry-run  # preview without writing
node scan-gmail.mjs --no-calendar

Acceptance criteria

  • node scan-gmail.mjs runs without LLM (pure API calls)
  • Detects interview confirmation emails (subject + body parsing)
  • Does not duplicate entries if run multiple times (dedup by company+role)
  • Auth flow documented in README
  • credentials.json and token.json in .gitignore
  • Works with Gmail personal accounts and Google Workspace accounts
  • All 65 CI tests pass (node test-all.mjs --quick)

Test plan

  • Run node scan-gmail.mjs --dry-run with real Gmail account — verify detected interviews print correctly
  • Run without --dry-run — verify TSV additions created in batch/tracker-additions/, prep files in interview-prep/
  • Run node merge-tracker.mjs after scan — verify applications.md updated with Interview status
  • Run twice — verify no duplicate entries created
  • Run node scan-gmail.mjs --no-calendar — verify no calendar event error when credentials lack Calendar scope
  • Run node test-all.mjs — all checks pass

Closes #660

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Automated detection of interview invitation emails with tracker updates, prep-file generation, and optional calendar event creation.
  • Documentation

    • Added a Gmail Interview Scanner guide with setup checklist, usage examples, and configuration snippet.
  • Tests

    • Added a validation run for the Gmail scanner (dry-run; allowed to fail without credentials).
  • Chores

    • Example config extended with Google OAuth settings; added npm script for scanning; ignored OAuth credential/token files; added Google API dependency.

Review Change Stack

…tion

Adds scan-gmail.mjs — a zero-LLM Gmail scanner that detects interview
invitation emails and automates downstream actions:
- OAuth2 auth flow with token persistence (calendar/token.json)
- Queries Gmail API with interview keyword filter (configurable days)
- Extracts company, role, date/time, meeting link, interviewer from email
- Deduplicates against existing applications.md entries
- Writes TSV tracker additions (status: Interview) via merge-tracker pattern
- Creates interview-prep/{company}-{role}.md stub files
- Adds Google Calendar events via Calendar API
- Flags calendar/credentials.json and token.json in .gitignore

Closes santifer#660

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

Welcome to career-ops, @tauheedbuttt! Thanks for your first PR.

A few things to know:

  • Tests will run automatically — check the status below
  • Make sure you've linked a related issue (required for features)
  • Read CONTRIBUTING.md if you haven't

We'll review your PR soon. Join our Discord if you have questions.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 16, 2026

📝 Walkthrough

Walkthrough

Adds CLI, docs, profile example, package script and dependency, .gitignore entries, a new scan-gmail.mjs that scans Gmail for interview invitations, extracts normalized fields, deduplicates against the tracker, writes TSV/markdown artifacts, and optionally creates Google Calendar events via OAuth2.

Changes

Gmail Interview Scanner

Layer / File(s) Summary
Configuration and project dependencies
package.json, config/profile.example.yml, .gitignore, README.md
Adds scan:gmail npm script and googleapis dependency; adds google config keys and example; ignores calendar/credentials.json and calendar/token.json; documents scanner setup and usage.
CLI entry, constants, and flags
scan-gmail.mjs (entry, constants, CLI parsing)
Defines search/query and regex patterns; implements --days, --dry-run, --no-calendar; loads profile and resolves credential/token paths.
OAuth2 client and interactive auth
scan-gmail.mjs (auth)
Load credentials, construct OAuth2 client, reuse/refresh persisted token or run interactive localhost callback flow with CSRF state; skip token persistence in dry-run.
Gmail message decoding and field extraction
scan-gmail.mjs (parsing helpers)
Decode MIME/base64 payloads, extract headers/body parts, and apply regex heuristics to extract company, role, interviewDate, meetingLink, and interviewer.
Deduplication and sequencing
scan-gmail.mjs (dedup logic)
Load existing applications.md entries into a set, skip interviews already at/past interview/offer/rejected statuses, and compute next tracker number for new entries.
Tracker TSV and interview-prep writers
scan-gmail.mjs (writers)
Create filename-safe slugs, write numbered TSV rows to batch/tracker-additions/, and emit per-interview markdown prep files in interview-prep/ only when absent.
Google Calendar event creation
scan-gmail.mjs (calendar writer)
Compose Calendar event payloads from extracted fields; insert timed 1-hour events or all-day fallbacks and record created event URLs or error strings.
Main orchestration and fatal handler
scan-gmail.mjs (main)
Orchestrate Gmail query, fetch full messages, parse/dedup/write artifacts, optionally add calendar events, print summary/errors, and exit non-zero on fatal failures.
Test harness integration
test-all.mjs
Refactor runner to pass explicit args and check expectExit; add scan-gmail.mjs --dry-run --days 7 with allowFail: true and expectExit: 1.

Sequence Diagram (high-level scan flow):

sequenceDiagram
  participant CLI as scan-gmail.mjs
  participant GmailAPI as Gmail API
  participant Parser as parseEmail()
  participant Deduper as dedup (applications.md)
  participant Writers as TSV & prep writers
  participant CalendarAPI as Google Calendar API
  CLI->>GmailAPI: query messages (subject filters, newer_than:N)
  GmailAPI-->>CLI: list of message IDs
  loop per message
    CLI->>GmailAPI: get full message payload
    CLI->>Parser: decode & extract fields (company, role, date, link)
    Parser-->>Deduper: normalized company::role
    Deduper-->>CLI: skip or new (next tracker number)
    alt new interview
      CLI->>Writers: write TSV + prep file
      alt calendar enabled and date present
        CLI->>CalendarAPI: insert event (timed or all-day)
        CalendarAPI-->>CLI: event URL / error
      end
    else skipped
      CLI-->>Writers: record skip
    end
  end
  CLI->>CLI: print summary, errors
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.63% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and specifically describes the main change: Google Workspace SDK integration for auto-detecting interview invites from Gmail, which matches the core objective of the changeset.
Linked Issues check ✅ Passed The PR implementation comprehensively addresses all coding requirements from issue #660: OAuth2 auth with token persistence, scan-gmail.mjs with Gmail querying, structured data extraction, deduplication, tracker/prep file generation, optional calendar events, config support, and zero-LLM design.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing the Gmail scanner feature: .gitignore (credentials), README (setup docs), config (google settings), package.json (dependencies/script), scan-gmail.mjs (main implementation), and test-all.mjs (testing integration).
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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 and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🤖 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 `@scan-gmail.mjs`:
- Around line 172-173: Prevent token persistence during dry-run by wrapping the
mkdirSync(path.dirname(TOKEN_PATH), { recursive: true }) and
writeFileSync(TOKEN_PATH, JSON.stringify(tokens, null, 2)) calls in a
conditional that checks the CLI dry-run flag (e.g., options.dryRun or DRY_RUN).
If dry-run is true, skip both filesystem operations and emit a log message
indicating the token would have been written; otherwise perform the existing
mkdirSync/writeFileSync. Apply the same conditional around the duplicate writes
at the second occurrence (the calls at the other location referenced by lines
487-488), using the TOKEN_PATH and tokens identifiers so the exact write sites
are covered.
- Around line 139-140: Generate a cryptographically strong random state value
(e.g., using crypto.randomBytes), include it in the call to
oAuth2Client.generateAuthUrl by passing state, persist that state server-side
(or in a signed cookie/session) tied to the user before redirecting, and in the
OAuth callback handler verify the returned state parameter matches the stored
one before calling oAuth2Client.getToken or exchanging the code; if it doesn’t
match, abort the flow and return an error. Update both the auth URL creation
site (where SCOPES and oAuth2Client.generateAuthUrl are used) and the callback
handling site (where the code is exchanged) to implement this generation,
storage, and strict validation using the same state identifier.
- Around line 30-32: The code currently hardcodes CREDENTIALS_PATH and
TOKEN_PATH and never reads the profile's
google.credentials_path/google.token_path; update initialization to load
PROFILE_PATH (profile.yml) via your existing YAML loader, read
profile.google.credentials_path and profile.google.token_path (if present), and
use those values to set the credential/token path variables (replace or override
CREDENTIALS_PATH/TOKEN_PATH with credentialsPath/tokenPath) before any code that
uses them (e.g., the sections around the earlier references and the code near
lines 76-83 and 483-485); ensure you fall back to the current defaults
('calendar/credentials.json' and 'calendar/token.json') when profile entries are
missing.
- Around line 71-73: The code reads the '--days' arg into daysIdx and sets
scanDays via parseInt but doesn't validate the parsed value; ensure that when
daysIdx !== -1 you check that args[daysIdx + 1] exists and that
parseInt(args[daysIdx + 1], 10) returns a positive integer (not NaN and > 0); if
validation fails, set scanDays to null or throw a clear error before building
the Gmail query (and in the code paths that build the query, e.g., the place
that adds the newer_than:<n>d clause, only append that clause when scanDays is a
valid positive integer) so you never produce an invalid newer_than: query that
would break the Gmail API.
- Around line 448-452: The all-day event fallback currently sets allDay = true
and assigns startDateTime and endDateTime to the same ISO date string (today),
which produces an invalid zero-length all-day event because Google Calendar
expects end.date to be exclusive; update the logic around the variables allDay,
startDateTime and endDateTime so that when allDay is true you set startDateTime
to today (YYYY-MM-DD) and set endDateTime to the next calendar day (compute
today + 1 day and format as YYYY-MM-DD), and apply the same fix to the other
occurrence that sets startDateTime/endDateTime (the block referenced at the
other location).
- Around line 145-149: The code currently builds a shell command using the
opener variable and execSync with string interpolation (execSync(`${opener}
"${authUrl}"`)) which can be subject to shell injection; update this to call
child_process.spawn or child_process.execFile directly (importing
spawn/execFile) and invoke it with the opener as the command and authUrl as a
separate argument (e.g., spawn(opener, [authUrl], { stdio: 'ignore', detached:
true }) or execFile(opener, [authUrl], { stdio: 'ignore' })). Replace the
execSync call in the try block with this non-shell invocation, ensure the child
is properly detached or awaited and any errors are caught in the existing catch,
and keep using the opener and authUrl symbols so the change is localized.

In `@test-all.mjs`:
- Line 72: The test entry for the CLI help is incorrectly marked as
allowed-to-fail; update the test entry in test-all.mjs for the item with name
'scan-gmail.mjs --help' (the object currently containing expectExit: 0 and
allowFail: true) to remove or set allowFail to false so the --help path is
enforced in CI; leave expectExit: 0 unchanged and keep the entry in the same
test array so the script still runs without OAuth credentials.
🪄 Autofix (Beta)

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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 44bc6f85-04ec-4ad9-a83c-5664fb9cee9f

📥 Commits

Reviewing files that changed from the base of the PR and between 5d1f3a3 and 4c1d03b.

📒 Files selected for processing (7)
  • .gitignore
  • README.md
  • calendar/.gitkeep
  • config/profile.example.yml
  • package.json
  • scan-gmail.mjs
  • test-all.mjs

Comment thread scan-gmail.mjs Outdated
Comment thread scan-gmail.mjs
Comment thread scan-gmail.mjs Outdated
Comment thread scan-gmail.mjs Outdated
Comment thread scan-gmail.mjs Outdated
Comment thread scan-gmail.mjs Outdated
Comment thread test-all.mjs Outdated
- Add CSRF state parameter to OAuth flow using crypto.randomBytes
- Replace execSync shell interpolation with execFile (shell injection fix)
- Skip token write during dry-run in both auth paths
- Read credentials_path and token_path from profile.yml google config
- Validate --days arg: must be positive integer, exit 1 on bad input
- Fix all-day calendar event: end date set to next day (Google Calendar end is exclusive)
- Update test-all.mjs: enforce scan-gmail graceful exit without credentials

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
test-all.mjs (2)

75-84: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

The expectExit field is defined but never validated.

The test loop checks only whether the command threw an exception (via result !== null), not whether the actual exit code matches expectExit. This means a script could exit with an unexpected code and still pass. For example, line 72 expects exit code 1, but if scan-gmail.mjs exits with code 2, the test will still warn as "expected without user data."

🔧 Proposed fix to validate exit codes

Update the run() helper to return both output and exit code, then validate against expectExit:

 function run(cmd, args = [], opts = {}) {
   try {
     if (Array.isArray(args) && args.length > 0) {
-      return execFileSync(cmd, args, { cwd: ROOT, encoding: 'utf-8', timeout: 30000, ...opts }).trim();
+      const output = execFileSync(cmd, args, { cwd: ROOT, encoding: 'utf-8', timeout: 30000, ...opts }).trim();
+      return { output, exitCode: 0 };
     }
-    return execSync(cmd, { cwd: ROOT, encoding: 'utf-8', timeout: 30000, ...opts }).trim();
+    const output = execSync(cmd, { cwd: ROOT, encoding: 'utf-8', timeout: 30000, ...opts }).trim();
+    return { output, exitCode: 0 };
   } catch (e) {
-    return null;
+    return { output: e.stdout?.toString() || '', exitCode: e.status ?? 1 };
   }
 }

Then update the test loop:

-for (const { name, allowFail } of scripts) {
-  const result = run('node', name.split(' '), { stdio: ['pipe', 'pipe', 'pipe'] });
-  if (result !== null) {
-    pass(`${name} runs OK`);
-  } else if (allowFail) {
+for (const { name, expectExit, allowFail } of scripts) {
+  const { exitCode } = run('node', name.split(' '), { stdio: ['pipe', 'pipe', 'pipe'] });
+  if (exitCode === expectExit) {
+    pass(`${name} runs OK (exit ${exitCode})`);
+  } else if (allowFail) {
     warn(`${name} exited with error (expected without user data)`);
   } else {
-    fail(`${name} crashed`);
+    fail(`${name} exited with code ${exitCode}, expected ${expectExit}`);
   }
 }

Note: This fix assumes execFileSync/execSync exceptions expose .status (Node.js does provide this on child_process errors).

🤖 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 `@test-all.mjs` around lines 75 - 84, The test loop uses result !== null to
detect failure but never checks the expected exit code, so scripts with wrong
exit codes still pass; update the run() helper to return both exitCode and
output (e.g., return { exitCode, stdout/stderr }) and change the loop over
scripts to compare the returned exitCode against the script's expectExit (fall
back to 0 if expectExit is undefined), calling pass() only when exitCode ===
expectExit, warn() when exitCode differs but allowFail is true, and fail()
otherwise; reference the run() function and the scripts array/expectExit
property in your changes.

76-76: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider pre-splitting arguments in test definitions.

The current approach of name.split(' ') assumes command arguments never contain spaces. While this works for the current hardcoded test entries, it's fragile. If a future test needs an argument containing spaces (e.g., --message "hello world"), the split will break.

♻️ Optional: Define args explicitly in test objects
 const scripts = [
-  { name: 'cv-sync-check.mjs', expectExit: 1, allowFail: true }, // fails without cv.md (normal in repo)
-  { name: 'verify-pipeline.mjs', expectExit: 0 },
-  { name: 'normalize-statuses.mjs', expectExit: 0 },
-  { name: 'dedup-tracker.mjs', expectExit: 0 },
-  { name: 'merge-tracker.mjs', expectExit: 0 },
-  { name: 'update-system.mjs check', expectExit: 0 },
-  { name: 'scan-gmail.mjs --dry-run --days 7', expectExit: 1, allowFail: true }, // exits without credentials (expected without setup)
+  { name: 'cv-sync-check.mjs', args: ['cv-sync-check.mjs'], expectExit: 1, allowFail: true },
+  { name: 'verify-pipeline.mjs', args: ['verify-pipeline.mjs'], expectExit: 0 },
+  { name: 'normalize-statuses.mjs', args: ['normalize-statuses.mjs'], expectExit: 0 },
+  { name: 'dedup-tracker.mjs', args: ['dedup-tracker.mjs'], expectExit: 0 },
+  { name: 'merge-tracker.mjs', args: ['merge-tracker.mjs'], expectExit: 0 },
+  { name: 'update-system.mjs check', args: ['update-system.mjs', 'check'], expectExit: 0 },
+  { name: 'scan-gmail.mjs --dry-run --days 7', args: ['scan-gmail.mjs', '--dry-run', '--days', '7'], expectExit: 1, allowFail: true },
 ];
 
-for (const { name, allowFail } of scripts) {
-  const result = run('node', name.split(' '), { stdio: ['pipe', 'pipe', 'pipe'] });
+for (const { name, args, expectExit, allowFail } of scripts) {
+  const result = run('node', args, { stdio: ['pipe', 'pipe', 'pipe'] });

This makes argument boundaries explicit and handles future edge cases.

🤖 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 `@test-all.mjs` at line 76, The test harness currently derives process
arguments by calling name.split(' '), which breaks when an argument contains
spaces; instead, update the test definitions to provide an explicit args array
(e.g., add an args property for each test entry) and change the invocation that
uses name.split(' ') to use that array directly (replace name.split(' ') with
the test.args array when calling run('node', ...)); ensure existing tests are
migrated to the new shape (single-word commands can use [name] or split once)
and keep the run(...) call signature unchanged.
🤖 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 `@scan-gmail.mjs`:
- Around line 38-41: The current SCOPES array always includes the calendar scope
causing authorize() to request calendar access even when noCalendar is set;
change the code to build the OAuth scopes dynamically based on the noCalendar
flag (e.g., compute scopes = [gmail.readonly] and conditionally push
calendar.events only if noCalendar is false) and pass that scopes variable into
authorize() and any other authorize calls (including the later occurrence around
the event-insert branch where SCOPES is referenced) so calendar permission is
only requested when events will actually be inserted.
- Around line 510-513: The profile value profile.google.gmail_scan_days is not
validated before being used to build the Gmail query (days/newer_than), so
invalid values can cause Gmail API errors; update the startup logic around
loadProfile/resolveGooglePaths to validate both the CLI override scanDays and
the configuredDays (profile?.google?.gmail_scan_days) to be a positive integer
(>=1), rejecting floats, zero, negatives, non-numeric strings, and missing
values with a clear startup error message (or fallback to a safe default like 7
only after explicit validation), and apply the same validation before the code
that composes newer_than:${days}d (also update similar usage at the other
occurrence referenced around lines with newer_than) while ensuring missing data
directories are handled gracefully per *.mjs guidelines.
- Around line 471-483: The current catch branch turns parse failures of
interview.interviewDate into an all-day event for "today", which is incorrect;
change the logic so that if interview.interviewDate is missing/empty you keep
the all-day fallback behavior, but if interview.interviewDate is present and new
Date(interview.interviewDate) fails, do not create a false event—instead set a
calendarError on the record (e.g., calendarError = `invalid interviewDate:
${interview.interviewDate}`) and skip event creation (return/continue) so
startDateTime/endDateTime and allDay are not written; update handling around
interview.interviewDate, the try/catch and any callers that rely on
startDateTime/endDateTime to respect the calendarError flag.

In `@test-all.mjs`:
- Line 72: Add a new test entry to the tests array in test-all.mjs for the help
case so the script's help exits successfully without OAuth credentials: insert
an object with name 'scan-gmail.mjs --help' and expectExit: 0 (do not set
allowFail: true). Place it alongside the existing test entries (near the
'scan-gmail.mjs --dry-run --days 7' entry) so the test runner verifies help
output succeeds even when credentials are missing.

---

Outside diff comments:
In `@test-all.mjs`:
- Around line 75-84: The test loop uses result !== null to detect failure but
never checks the expected exit code, so scripts with wrong exit codes still
pass; update the run() helper to return both exitCode and output (e.g., return {
exitCode, stdout/stderr }) and change the loop over scripts to compare the
returned exitCode against the script's expectExit (fall back to 0 if expectExit
is undefined), calling pass() only when exitCode === expectExit, warn() when
exitCode differs but allowFail is true, and fail() otherwise; reference the
run() function and the scripts array/expectExit property in your changes.
- Line 76: The test harness currently derives process arguments by calling
name.split(' '), which breaks when an argument contains spaces; instead, update
the test definitions to provide an explicit args array (e.g., add an args
property for each test entry) and change the invocation that uses name.split('
') to use that array directly (replace name.split(' ') with the test.args array
when calling run('node', ...)); ensure existing tests are migrated to the new
shape (single-word commands can use [name] or split once) and keep the run(...)
call signature unchanged.
🪄 Autofix (Beta)

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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 71b64f14-97d6-4b4e-a428-55564dd47ad1

📥 Commits

Reviewing files that changed from the base of the PR and between 4c1d03b and da84b70.

📒 Files selected for processing (2)
  • scan-gmail.mjs
  • test-all.mjs

Comment thread scan-gmail.mjs Outdated
Comment thread scan-gmail.mjs Outdated
Comment thread scan-gmail.mjs
Comment thread test-all.mjs Outdated
- Dynamic OAuth scopes: only request calendar.events scope when --no-calendar
  is not set; Gmail-only runs no longer prompt for unnecessary permissions
- Validate profile gmail_scan_days: must be positive integer, exit 1 on invalid
- Fix calendar fallback logic: if interviewDate is present but unparseable,
  throw a descriptive error (no false today event); all-day fallback only used
  when interviewDate is absent
- test-all.mjs: add explicit args array to each script entry (no name.split)
- test-all.mjs: add runScript() helper that returns actual exit code; loop now
  validates exitCode === expectExit instead of null check

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
test-all.mjs (1)

66-72: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Missing --help test flagged in past reviews.

The test array includes a --dry-run test (line 72) but still lacks the --help test requested in previous reviews. Help output should succeed without OAuth credentials and be enforced in CI to catch regressions in the basic CLI path.

➕ Add the missing --help test
   { name: 'merge-tracker.mjs',            args: ['merge-tracker.mjs'],                                   expectExit: 0 },
   { name: 'update-system.mjs check',      args: ['update-system.mjs', 'check'],                          expectExit: 0 },
+  { name: 'scan-gmail.mjs --help',        args: ['scan-gmail.mjs', '--help'],                            expectExit: 0 },
   { name: 'scan-gmail.mjs --dry-run --days 7', args: ['scan-gmail.mjs', '--dry-run', '--days', '7'],     expectExit: 1, allowFail: true },
 ];
🤖 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 `@test-all.mjs` around lines 66 - 72, Add a test entry for the CLI help
invocation so the basic CLI path is validated without OAuth: insert an object
similar to the existing entries (e.g. { name: 'scan-gmail.mjs --help', args:
['scan-gmail.mjs', '--help'], expectExit: 0 }) alongside other tests in the test
array (near 'scan-gmail.mjs --dry-run --days 7'); ensure expectExit is 0 and do
not set allowFail (or set allowFail: false) so CI will enforce it.
🤖 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 `@scan-gmail.mjs`:
- Around line 500-501: The event start/end objects currently use either { date:
startDateTime } or { dateTime: startDateTime } which omits timezone and can
mis-schedule events; update the branch that builds { start: { dateTime:
startDateTime }, end: { dateTime: endDateTime } } to include an explicit
timeZone property (e.g., { dateTime: startDateTime, timeZone: userTimeZone })
and obtain userTimeZone from a reliable source such as
Intl.DateTimeFormat().resolvedOptions().timeZone or the calendar settings,
and/or fall back to a configured default; also add a brief comment documenting
the limitation if timezone cannot be determined.
- Around line 462-488: The else branch that creates an all-day placeholder is
dead due to the early return at the top of addCalendarEvent; remove the
unreachable fallback block (the entire else { /* No date detected ... */ }
including the today/tomorrow logic and the allDay variable) and any now-unused
variables or comments (e.g., remove allDay and any related slicing logic) so
addCalendarEvent only handles the parsed interview.interviewDate path; keep the
initial guard (if (!interview.interviewDate) return null) and the
startDateTime/endDateTime assignment inside the parsed branch.

---

Duplicate comments:
In `@test-all.mjs`:
- Around line 66-72: Add a test entry for the CLI help invocation so the basic
CLI path is validated without OAuth: insert an object similar to the existing
entries (e.g. { name: 'scan-gmail.mjs --help', args: ['scan-gmail.mjs',
'--help'], expectExit: 0 }) alongside other tests in the test array (near
'scan-gmail.mjs --dry-run --days 7'); ensure expectExit is 0 and do not set
allowFail (or set allowFail: false) so CI will enforce it.
🪄 Autofix (Beta)

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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 33f0ffe8-8c45-401b-a026-f42bb5ac08cc

📥 Commits

Reviewing files that changed from the base of the PR and between da84b70 and 5ffe0f0.

📒 Files selected for processing (2)
  • scan-gmail.mjs
  • test-all.mjs

Comment thread scan-gmail.mjs
Comment thread scan-gmail.mjs Outdated
- Add --help/-h handler: prints usage and exits 0 (no credentials needed)
- Remove dead all-day fallback in addCalendarEvent: early return guard makes
  else branch unreachable; function now only handles present+parseable dates
- Add timeZone to calendar event start/end via Intl.DateTimeFormat to prevent
  mis-scheduling across DST boundaries
- test-all.mjs: add scan-gmail.mjs --help test with expectExit: 0, no allowFail

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@tauheedbuttt
Copy link
Copy Markdown
Author

Hi! Could a maintainer please approve the workflow runs? This is my first contribution to the repo. All CodeRabbit comments have been resolved across 3 rounds of review. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Google Workspace SDK integration — auto-detect interview invites from Gmail and sync to calendar + prep files

1 participant