Google Calendar
Samaya syncs your Google calendars into calendar.org in your Org folder. The
app syncs while it is open; samaya sync run keeps the file fresh for
org-agenda while it is closed.
Connecting an account
In the app, open Settings → Calendars → + Connect calendar. Give the account a name, then sign in with Google in the browser that opens.
An account connected this way syncs every calendar it has, including ones you
add or are shared later. Each calendar has a switch that hides it from Samaya's
views (it still syncs into calendar.org) and a colour, starting from its
colour in Google. The account's card re-authorizes and disconnects it. Sync all
calendars does the same for an account connected from the terminal.
From a terminal:
samaya sync auth googleIt asks for an account name, the Org tag its events should carry and, the first time, your Org folder. It then opens your browser, and once you have signed in, lists the account's calendars so you can choose which to sync. Every prompt has a flag:
samaya sync auth google \
--account work --tag work \
--calendar primary --calendar team@example.com \
--no-interactiveSamaya asks to read your calendars and to edit events (calendar.readonly and
calendar.events), so it can create task events, change event lengths and
answer invitations.
Your own OAuth client (optional)
Connecting needs no setup: Samaya signs in through its own OAuth client. Use your own instead to have your own API quota, or if your organization only allows its own apps. In the connect form, choose Sign in through → Your own app.
Create a project at https://console.cloud.google.com/projectcreate.
Enable the Google Calendar API for it.
Configure the OAuth consent screen with the External user type.
Set the publishing status to "In production". A client left in Testing issues sign-ins that expire after seven days, so every account would need re-authorizing each week. Publishing without verification costs one "Google hasn't verified this app" screen, clicked through once per account.
Create an OAuth client ID of type Desktop app. Keep the client ID and client secret to hand.
From a terminal, pass them to the first account you connect. Later accounts reuse its client.
samaya sync auth google --account work \
--client-id ....apps.googleusercontent.com --client-secret ...On Google Workspace, a client with user type Internal avoids the warning screen, but can only sign in accounts in that organization. One External client covering work and personal accounts is usually simpler.
Scheduling tasks into a calendar
Choose a calendar in Settings → Calendars → New task events. Dropping a task onto a time slot then creates an event in that calendar with the task's title, notes and duration. The block keeps the task's channel colour.
Moving or stretching the block updates its event.
Changing New task events affects newly scheduled tasks only.
Local tasks only keeps new time blocks in the planner.
Removing a task's time block leaves its calendar event in place.
Failed writes retry on later syncs, including after a restart. Settings → Calendars shows their progress, or a retry action. Pausing sync pauses them too.
Answering invitations
An event's card, opened on hover or click, has a Going? row with Yes, No
and Maybe for any upcoming invitation someone else organised. A reply changes
only your own response, for that occurrence only, and Google tells the
organiser. calendar.org picks up the new state straight after.
An account connected before replies were supported has read access only.
Re-authorize it once from Settings → Calendars, or with samaya sync reauth,
and accept the new permission.
Re-authorizing
When an account's sign-in expires, the tray reports that it needs re-authorization. Re-authorize it from its card in Settings → Calendars, or from a terminal:
samaya sync reauth --account personalThis signs in again and changes nothing else. Sign in as the same Google
account; a different one is refused. A running samaya sync run --watch needs
no restart.
Changing an account's calendars or tag
samaya sync auth refuses a name that is already connected. To change an
account on purpose:
samaya sync auth google --account personal --reconfigureEvery prompt defaults to what the account already has. The calendar picker marks the calendars currently synced, and a blank answer keeps them.
Syncing from the terminal
samaya sync run --once # sync once, then exit
samaya sync run --watch # sync now, then every interval
samaya sync status # accounts, calendars, events and last sync
samaya sync reauth --account work
samaya sync disconnect --account workWhile the app is open it does the syncing, and samaya sync run refuses with
an error. status, auth, reauth and disconnect always work.
To sync while the app is closed, use either a systemd user service with
--watch, as in Getting
started, or a timer with --once. Enable one, not both.
For a timer, ~/.config/systemd/user/samaya-sync.service:
[Unit]
Description=Samaya calendar synchronization
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=%h/.local/bin/samaya sync run --once
and ~/.config/systemd/user/samaya-sync.timer:
[Unit]
Description=Run Samaya calendar synchronization periodically
[Timer]
OnBootSec=2min
OnUnitActiveSec=5min
Persistent=true
[Install]
WantedBy=timers.target
systemctl --user enable --now samaya-sync.timerWhile the app is open, the timer's runs are refused; they resume once it closes.
Configuration
~/.config/samaya/sync.toml holds no secrets:
[sync]
org_directory = "/home/you/org" # the folder the planner uses
interval_minutes = 5
[sync.window]
past_days = 30
future_days = 180
[google.work]
calendars = ["primary", "team@example.com"]
tag = "work"
interval_minutesis how often--watchsyncs.windowis a rolling span of days around today. A widerfuture_daysmakes a longer file: a daily standup over 180 days is about 130 entries.calendarslists the calendars to sync;all_calendars = truesyncs every calendar on the account instead.tagis the Org tag on the account's events; it defaults to the account's name.
Samaya writes the other fields of an account's table itself. Leave them as they are.
Credentials
Client secrets and sign-ins go to the system credential store: Secret Service
on Linux, Credential Manager on Windows. Where none is reachable, as on most
servers, they are kept in files under ~/.local/share/samaya/credentials/,
readable only by you.
A locked keyring is reported as such, and background syncs never prompt. If a
--watch service starts before your session unlocks the keyring, its first
sync fails and the next one succeeds.
The calendar file
calendar.org is generated, and every sync rewrites it. Don't edit the
generated parts of an event: its headline, planning line, :PROPERTIES: and
:DETAILS:. Notes you write beneath an event are kept.
#+TITLE: Calendar
#+TODO: MEETING ACCEPTED TENTATIVE EVENT | ATTENDED DECLINED CANCELLED
* MEETING Design review :work:
DEADLINE: <2026-08-10 Mon 14:00-15:00>
:PROPERTIES:
:ID: gcal-work-...
:CALENDAR: primary
:CONFERENCE: https://meet.google.com/...
:LOCATION: Meeting room 2
:URL: https://calendar.example.com/event?eid=...
:END:
:DETAILS:
Attendees:
- Alice Smith <alice@example.com> :: organiser, accepted
- you@example.com :: you, awaiting
Description:
Quarterly roadmap review.
:END:
Bring the roadmap doc.
* EVENT Dentist :personal:
DEADLINE: <2026-08-11 Tue 11:00-12:00>
:PROPERTIES:
:ID: gcal-personal-...
:CALENDAR: primary
:LOCATION: 12 High Street
:END:
"Bring the roadmap doc." is a note: it stays beneath its event across syncs.
| Keyword | Meaning |
MEETING | An invitation you have not answered: the only one that is a task |
ACCEPTED | An invitation you accepted, still to come |
TENTATIVE | An invitation you answered Maybe, still to come |
EVENT | An event that asked nothing of you: your own entry, a birthday |
ATTENDED | An event that has ended |
DECLINED | An invitation you declined |
CANCELLED | An event that was called off |
The done keywords keep past and declined events out of
org-agenda's task views, and a TODO match onMEETINGlists the replies you owe.Times are on
DEADLINE:. All-day events have a bare date; an event over several days records its last day in:EVENT_END:.:CONFERENCE:holds the meeting link, soorg-open-at-pointopens it.Each event is tagged with its account's tag, for filtering in
org-agenda.:DETAILS:lists the guests, withyou,organiserand each response (accepted,declined,maybeorawaiting), then the description.Recurring events appear as one entry per occurrence.
archive/calendar.org
When an event with a note goes away, because it was deleted, aged out of the
window, or its calendar or account was removed, its entry moves to
archive/calendar.org with the note. Events without notes are simply dropped.
Samaya only ever adds to this file, so notes there are safe to edit. It is
tagged :ARCHIVE:, so org-agenda leaves it out.
If an account can't be reached, its events and notes stay in calendar.org
until it can.
Troubleshooting
samaya sync status names any account that needs signing in again;
samaya sync reauth --account <name> fixes it. Other accounts keep syncing
meanwhile, and the account's last known events stay in the file.
If the browser shows "Something went wrong"
(accounts.google.com/info/unknownerror), the browser's Google session is
usually confused by several signed-in accounts or a stale cookie. Copy the
address the command prints into a private window; the command keeps waiting for
it.
| What Google says | What it means |
invalid_client / "OAuth client was not found" | Wrong client ID, or a client created moments ago that is not ready yet |
redirect_uri_mismatch | The client is a Web application; it needs to be a Desktop app |
| "Access blocked: … has not completed the Google verification process" | The consent screen is in Testing and this account is not a test user. Publish it, or add the account |
| "Something went wrong" with no code | The browser session, as above, or a project whose consent screen was never configured |
