Privacy architecture
Matching runs on your machine, and your profile does not cross the wire. You can check that in the engine itself: the plugin ships the bundled CLI, and that bundle is public in the plugin mirror. The website is different — signing in, posting and claiming work, and analytics run on our servers, and the sections below say what each of them collects.
What never leaves your machine
The CLI downloads a public job index and derives your skill profile from your public GitHub signal and, optionally, local telemetry you explicitly enable. The matcher and your profile stay on your machine: no scores, no skill tags, no fingerprint, no telemetry.
Match results are the exception, and it is narrow. After the CLI matches you
against other builders, it posts the public logins of the people you matched, so each
of them can see "you were matched N×" on their own dashboard. The request carries no
cookie, no auth header and nothing from your profile
(apps/cli/bin/directory.js),
but it does leave your machine, with your IP, and it is a function of your profile.
Item 17 below covers the patch you send when you claim a paid task.
An automated test suite (the "zero-egress" scan) enforces that every network call in
the CLI's own bin/ and src/ files is individually allowlisted and justified,
and CI fails if anyone adds one. Read its edge honestly: the scan reads those
directories, not the workspace packages the CLI bundles into its binary, so network
code in packages/* is outside what that test can see.
The profile on disk, and the one command that sends it
The profile the matcher reads sits encrypted under ~/.terminalhire/. You can
read it, change it or destroy it without touching the network:
terminalhire profile --show # print it, decrypted, to your own terminal
terminalhire profile --edit # display name, contact email, remote-only, comp floor
terminalhire profile --delete # wipe the profile and its key from disk
On a machine that has never signed in, --show says so and points at
terminalhire login instead of printing empty fields. --edit asks for those
four values in turn and keeps the current one whenever you press Enter.
--delete asks you to type yes, and takes the encryption key with it.
Sending that profile to us is a separate command you have to ask for:
terminalhire sync --push # or --enable, which does the same thing
terminalhire sync --status # reads a local file; makes no network call
terminalhire sync --delete # hard-delete what you sent
sync --push prints the exact field list first — GitHub login, public email,
top languages, skill tags, and your display name and contact email if you have
set them — alongside what is never sent. Press Enter and it opens your browser.
The consent itself is given on that page, and your profile goes only once the
page has said yes; the CLI waits ten minutes for that and then gives up. One
snapshot goes, and nothing more until you run the command again.
sync --status reports what a local marker says, which is a weaker thing than
asking us what we hold. sync --delete asks you to type yes and then deletes
the copy on our side. It needs a token the push wrote, so it has to run from the
machine that pushed.
What ever crosses the wire
Each one explicit, below.
The authoritative list is not this page — it is
apps/cli/test/zero-egress.test.js,
which fails CI if anyone adds a network call the allowlist does not name. Where that test
and this page disagree, the test is what the software actually does, and this page has the
bug.
That wording is deliberate. This section used to open "these, and only these", and the
sentence was wrong twice — the pulse prompt and the feedback command were both missing
while it read as a guarantee. An absolute maintained by hand degrades every time someone
adds a request and forgets the docs.
So a second test, apps/web/__tests__/privacy-covers-egress.test.ts, now fails the build
if a request is added to that allowlist without someone recording which item below tells
you about it. It checks coverage, not wording: whether a paragraph describes a request
honestly still takes a person, and no test will catch that for us.
A consented lead. If you choose to share your profile with a recruiter or accept an intro, that specific, consented payload is sent — after a typed yes. Sending your profile with
sync --pushis the same kind of send; there the consent is given in the browser, as described above.Anonymous click analytics. Job links route through a redirect that counts clicks in aggregate. No profile data rides along.
Opt-in connection features. Publishing to the developer directory, requesting intros, and chat are all double-opt-in. Chat is end-to-end encrypted (X25519 + XChaCha20-Poly1305) — the server stores ciphertext it cannot read. Posting from the terminal is opt-in too.
terminalhire setupsigns this terminal in through the same approval page an AI tool uses and keeps the key it receives encrypted on this machine. It sends the repository name you give it, or your checkout's git remote, and the email address you type for notices.terminalhire post submitsends your draft together with the branch, remote and changed file paths it read from your checkout, after redacting the credential patterns it recognises, and prints the exact body before sending anything.post publishsends the draft's id, the repository name, the price you set and any brief fields you pass. Connecting GitHub turns two display settings on by default — listing the individual merged pull requests behind your totals, and building the dossier that describes them. Neither publishes anything on its own: your profile page stays unpublished until you publish it. Turning PR-link display on enables repository names both on your published public résumé and on the private poster decision card when you claim someone's posting. Both cover work that is already public on GitHub, both are on the settings tab to switch off, and turning one off keeps it off — including across a later reconnect. Keeping a GitHub token is not part of that default; it is a separate explicit choice, below, because it is the only one of these that stores a credential.A stored GitHub token — explicit,
read:useronly. Off until you turn on "live checks" or accept the dashboard's "show my full record" prompt. It lets us read your merged pull requests on your own rate limit.read:useris the only scope the developer sign-in ever requests, so this token reads public data and nothing else — it cannot see a private repository. It is encrypted at rest, scoped to your session, and turning the setting off clears it from every session you have.Posting a paid task is a different grant, and a much larger one. A poster installs a separate GitHub App on the repositories they choose on GitHub, which can be all of them; for each posting it mints a short-lived token scoped to that posting's one repository, which can read it — including a private one — and push a patch on the poster's behalf. It is never installed on a developer's account, and it plays no part in sign-in.
Opt-in click attribution. Off by default. If you turn on "Share job click activity" in the dashboard, and only while you are signed in, a job link you open is also recorded against your GitHub login so you can see your own recent clicks. Anyone signed out, or opted out, stays on the anonymous path above — nothing is attributed. Turning it back off deletes the history it collected, not just stops new rows.
Anonymous install/init counters. Downloads of the install script and completed
terminalhire initruns are counted as anonymous daily aggregates — a date, an optional campaign tag from the link you followed, and a count. The install-script download additionally records a coarse client kind (CLI pipe vs. browser view) — never the raw user-agent string. No account, IP address, or device information is stored or associated; the tag travels only in the URL and carries nothing about you.The "Proof of Work" newsletter — double opt-in. If you sign up, we store your email address — and, only if you choose to add it after confirming, your GitHub username. Nothing else that identifies you (optionally a campaign tag from the link you followed — never an IP address, user-agent, or cookie). We send one confirmation email; you are only a subscriber once you click to confirm. The optional GitHub username is used only to pick issues that match your stack, never linked to your résumé or profile, and deleted with your email. If you add it, we also read your public GitHub following list so we can occasionally pick issues from maintainers you already follow ("From your orbit") — that list is used only to choose picks, never shown to anyone, and deleted with your email. An unconfirmed signup is auto-deleted after 7 days. Every issue carries a one-click unsubscribe that hard-deletes your email — not archived, not suppression-listed, deleted. Delivery is handled by our email processor (Resend); the list itself lives in our own database, disjoint from any profile or résumé data.
"Check against my GitHub" on a project page — public data, nothing stored. On a public project page you can, while signed in, ask whether your work fits its open issues. The check is computed server-side from your public GitHub only — the same public proof-of-work signal that backs your résumé (which domains you have merged pull requests into), matched against that project's issues. It reads nothing from your local machine or profile — your local profile never reaches the server, here or anywhere. The result is computed, returned, and discarded: we write no record keyed to you and that project, and the answer is never shown to anyone but you. If your public credential can't be verified at that moment (GitHub is rate-limiting us, say), you get an honest "can't verify right now" — never a fabricated score.
Contribution-dossier synthesis — envelope-derived facts only, off by default. The Contribution Dossier can optionally use an LLM (Anthropic) to phrase a few narrative sections and a résumé bullet from your merged work. What the model receives is only the identity-free dossier envelope and deterministic counts derived from it — pseudonym labels, enums, sizes, timestamps. No third-party free text is ever sent to the provider: not a comment body, not a review comment, not an issue description — nothing written by another person. Third-party maintainers appear to the model only as stable pseudonyms, never as a name, login, or email. Every generated sentence must cite the specific envelope fact that supports it, a second pass discards any sentence those facts don't back, and anything that slips through those checks is dropped — so the output can't say more than your structured record already proves. The feature is off unless explicitly enabled by the operator, and it runs only for a signed-in developer who has opted the dossier in — which, since connecting GitHub defaults that setting on (see "Opt-in connection features" above), means anyone who has connected and not switched it off. It also needs a stored GitHub token, which is never defaulted on, so no build runs on a connect alone. Deleting your account, or a maintainer's Article-17 request, deletes the synthesized text alongside the record it was derived from.
Notification and reminder emails. If you post a paid task, we email you when something on your posting needs you: a claim, an arriving patch, finished checks, a verdict waiting, a blocked submission, a request to resolve, a note from the developer. We send to the contact address you confirmed when you posted — that address is stored, and publishing asks for it. If there is none, we fall back to the public email on your GitHub profile, read at the moment we send and not stored; with neither, no email goes out and nothing else changes. The emails carry no developer's name or identity: they say what happened and link to the row on your own dashboard, which grants nothing to anyone not already signed in as you. If you claim a paid task, we email you when the poster acts on your claim — a change request (with the poster's own words in it), approval to start, a declined request, acceptance, payment, the posting coming down — at the contact address you confirmed, or else your GitHub public email. Those emails name the posting only by its short reference. Reminders: when something we emailed you about is still waiting on you a day later — or a setup step you started (saving a card, confirming an email) is unfinished, or an intro request to you is unanswered — we may send up to three reminders, a day apart, to the same address the first email used; a contact-email reminder is not sent to the address awaiting confirmation unless it is also your GitHub public email. A reminder carries nothing a person typed. To send each one only once we keep a small record of it — which rule, an id for the claim, posting, intro, draft or account it was about, when, and whether it went — not the address, and we delete it after 30 days (records keyed on your account go sooner, when you delete it — item 21). Every one of these emails has a one-click link to stop it, except the emails about a payment we are holding or have already taken, which always go out; and Settings has a switch for each kind, reminders included. New paid tasks: when a new paid task appears on the public listing, we email every developer we have an address for, once per task, unless you have turned these emails off. That is every account with a contact email confirmed with us or a GitHub public email on a current sign-in, except the task's own poster, an account that has posted tasks and never claimed one (we treat it as a poster, not a developer, and do not send it these emails at all), an account set to poster-only (developer surfaces turned off), an account whose posting access we revoked, and a deleted account. It goes to the contact address you confirmed, or else your GitHub public email, read when we send. It carries only what the public listing shows — the task's public title, its price, its short reference and a link to claim it — and never the poster's name or the repository. Each one has a one-click link that stops these emails and only these, and Settings has a switch for them; turning off all claim emails or all posting emails does not turn these off. To send each one only once we keep a record of it — the task, your numeric GitHub account id, when, and whether it went — not the address, and we delete it after 30 days, or when you delete your account (item 21). Separately we keep, with no end date, a list of every task we have come across and whether we announced it; it names no person. Delivery is handled by our email processor (Resend). Separately, for every account, poster or developer, we store which notifications in your dashboard bell you have marked read — the time you last marked everything read, and the ids of the rows marked read one at a time (rows you opened, and rows the mark-all button named individually), each with the time of the event it is about; the ids name the claim, posting or intro each row is about.
A release recall check — the one request that sends nothing.
terminalhire runexecutes a repository's own build and test commands on your machine. Before it does, it asks whether the version you are running has been withdrawn — so a release we discover a containment defect in can be stopped without waiting for you to upgrade. The request carries no query, no body, no cookie, no token and nothing about you, your machine, your profile or the repository. This request does not tell us which version you run: the server returns the full list of withdrawn versions and the comparison happens on your machine. (The pulse prompt below does send your CLI version, if you answer it — so this is a statement about this request, not about the product.) If the check cannot reach us,runproceeds — being offline is not a reason to stop you working. Once your version has been shown to be withdrawn, that answer is remembered locally and keeps applying offline. Once a version has been withdrawn and then cleared again, a reachable check deletes that local record — so an un-recalled build does not keep refusing to run offline.The pulse prompt — one digit, only if you answer it. If you have opted into the beta programme and are signed in, the CLI occasionally asks how things are going and offers a 0–3 rating. Pressing anything else, including Enter, sends nothing — the request is never made. If you do answer, we send exactly four things: that single digit, your CLI version, your operating system name (e.g.
darwin,linux,win32), and your session cookie so it is attributed to your account. No free text is ever sent — there is no comment box, and no field of this request can carry one. Theterminalhire feedbackcommand below is a different thing on the same endpoint, and it is a comment box.terminalhire feedback— free text, by design, and only after you read it back. This one exists to carry your words, so it sends what you typed: a category, whichever of the free-text answers you filled in (what you were trying to do, expected versus actual, what almost made you quit, whether you will keep it installed), any 1–5 ratings you gave, plus your CLI version and operating system name. It is sent with your session cookie, so it is attributed to your account. Before anything is sent, the CLI prints the exact bytes of the request and requires you to typeyes— anything else cancels with zero network calls. Nothing is gathered in the background; every field is one you typed at a prompt.Joining or leaving the beta programme.
terminalhire betasends your choice about whether to be listed publicly, with your session cookie. Re-running it to check your status sends a fixed, empty body carrying nothing you wrote. Without a linked session it sends nothing at all.Telling a poster why you're passing — one of five fixed answers, stored without your name. On the
bountieslist,d<number>sends the posting's id and one value from a fixed list: the price is low, the scope is unclear, there's no way to check the work, not my stack, or something else. There is no free-text box, so there is nothing to send that you did not pick from a menu. Your session cookie authenticates it and then the server throws the identity away: the stored row holds a keyed hash, different on every posting, so your answers cannot be joined into a history of what you turned down. Posters see counts only, and the breakdown on a posting stays hidden until at least three people have answered. The first time, the CLI prints the exact bytes and waits for you to typeyes. This is a separate act from dismissing a job — your local job statuses still never leave your machine, and passing never sends them.terminalhire decline --forgetclears the local consent only: the next time you pass on a posting, the CLI asks again before sending anything. It does not delete answers you already sent, and there is no endpoint that does. That is deliberate, not an omission. The stored row is a keyed hash with no login column and a different value on every posting — it is not attributable to you, cannot be looked up by your name, and cannot be assembled into a history — so it is not personal data we hold about you, and it sits outside the erasure machinery that covers things like intro contact details. What you would be asking us to delete, we cannot find, because we made sure we never could.A note you write to a poster — your words, kept for as long as the message is.
terminalhire claim notesends a note to the poster of work you have claimed, once you have turned CLI sending on in your dashboard. It is stored twice: as the message itself, and as a send record beside it that holds the text and a hash of it. Both age out together. A send record left behind by a note that never went out is deleted after 7 days. A record of a note that did go out keeps its row, but the text inside it is emptied on the same 180-day clock that redacts the message. What survives is a hash, a timestamp and the id of the message, none of which recovers a word you wrote. We also keep whether CLI sending is on for you, and when you turned it on. If a poster reports a note, we keep that the report was made and try to email the note's text to our operators so someone can read it; an email that does not go through is tried again when the note is next reported. Deleting your account removes your send records, sent and unsent, and your CLI sending setting, on the same path that clears the rest of your record; a poster who deletes theirs takes their reports with it.Claiming a paid task sends your code. This is the largest thing a developer sends, and it stayed off this list too long. When you submit a claim, the CLI posts your patch — the diff you wrote — to our servers, along with the results of any verification runs. The poster who owns that posting reads it; that is the point of sending it. It is not anonymous: it is stored against your claim, and it is what the accept-or-ask-for-changes decision is made on. Item 21's "kept, with your name removed" paragraph covers claims, postings and events; the patch itself and its run records are not named there, so treat a submitted patch as something you cannot unsend.
A verification run we execute for you.
terminalhire runruns on your machine and sends nothing — unless you ask it not to.--placement hostedis you choosing a cloud VM for that run, so the repository and your patch go to it; the default,local-docker, is the one that leaves nothing. A run dispatched by a posting is different: your patch is applied and executed on a machine we operate — a worker, or a cloud VM booted for that run — inside the fence described in How verification runs work. The code under test is the repository's plus yours.A repository's own contribution rules, read by a model — your key, your choice. Before claiming open-source work, the CLI reads the target repository's public policy documents (CONTRIBUTING, AGENTS, the PR template) to check whether AI-assisted work is allowed. A keyword scan runs locally. A second, semantic read sends that public policy text to Anthropic's API, and only when you have set
ANTHROPIC_API_KEYyourself — no key, no request. Nothing of yours rides along: not your profile, not your fingerprint, not the claim. The payload is a public document from someone else's repository, plus that repository's name — which does say which repo you are looking at, so it is named here rather than left out.Keeping a poster posted — only if you asked for that.
terminalhire claim --push --keep-updatedturns on two sends, and nothing here runs without it: a plainclaim --pushsends none of it. The first is a periodic snapshot of your own claim, at most once a day and only when something changed. It is deliberately score-free — no acceptance score, no blockers, no branch, no worktree path, no policy verdict. The second is a presence beat that carries exactly the posting id, the claim id and a push token, so the poster's page can say "active 4 minutes ago"; the server stamps the time itself. No note, no progress, no rating rides along, and it fires at most once every 30 seconds. Both are authenticated by a hashed, push-only token bound to your login — never your GitHub token — and both fail silently rather than interrupting you.The two differ in what they cover, and the difference matters. The presence beat needs a server claim id, which only a claim on someone's posting has, so an open-source claim never sends one. The daily snapshot is not scoped that way. It sends every claim on this machine, open-source ones included — for each, its kind, the repository it is against, its state, whether it merged, its pull-request link and its timestamps. If you have open-source claims and you turn this on, the repositories you are working in are part of what goes.
claim --push --revokestops both.Deleting your account — one button, and here is exactly what it does. The control sits under "danger zone" in your dashboard settings, beside the one that ends this browser's session. It asks once to confirm, and there is no undo.
Deleted outright: your connection row, which holds your name, avatar, follower counts, the seniority and skill tags we inferred, your cached résumé and every consent flag on it. Your dossier — the scored contribution records and the synthesized bullets built from them. Note drafts, sent and unsent. Your attributed job-click history. Your trajectory attestation. Push tokens, web-push subscriptions and the delete capability the CLI holds for your pushed claims, so nothing can reach you or act as you afterwards. The claims you pushed from the CLI. Your published profile — the name, addresses, languages and tags you synced. Your beta membership, your place on the waitlist and the feedback you sent us. The record of which repositories you asked us to look at, which we keep only to rate-limit the asking. Intros you requested, and the conversations attached to them. The records of reminders we sent you about saving a card or confirming an email (item 10), and from the moment you ask, no reminder of any kind is sent to your account again. The records of the new-paid-task emails we sent you (item 10), and none is sent to your account again. The record of which dashboard notifications you have read.
Every MCP connector you created, so a token minted for one stops working. If you connected an editor or an agent to post paid tasks through us, that connection carries a token of its own, and a token is not a session — it does not expire when you sign out, and until now it kept working after the account it acted for was gone. Any approval you had started but not yet finished goes too, so nothing left half-done can turn into a working token afterwards. Anything still holding one will simply be refused.
Every message you sent, on every conversation. Not only the ones on intros you started. A conversation someone else opened with you keeps their request, so the messages on it do not go with it — they are deleted by name instead. Your chat key, your presence row, your read positions, and any block either of you placed go with them: a block on a login that names nobody protects nobody, and all it still records is that the two of you had dealings.
And every session row of your account — the browser you clicked in, your other devices, and any
terminalhire linkbridge — so you are signed out everywhere, not just here.Kept, with your name removed: claims, postings and the events behind them. Those are the record of paid work, and someone else's money moved through them. The rows stay so the accounting still adds up; your login and your numeric GitHub id come out of them, and the free text a poster wrote about you is emptied. Deleting the rows outright is not the safer option — it once stranded a posting so that it could never be withdrawn.
Kept, still naming you, and here is why: an intro someone else requested with you as the target keeps their row and their record of whom they asked. Your contact details and the id we would mail you at are cleared from it. That request is theirs, not yours, and deleting it would erase a person who did not ask to be erased. If you have ever asked us to anonymize you as a repo maintainer, the marker recording that survives too, because it is the thing that stops your name coming back the next time we read that repository.
Not touched, and each for its own reason. A poster's saved card and a developer's payout account are financial records held with Stripe, not ours to drop on the way past. Our record of each payout we made to you stays, with your GitHub account id on it, because we keep payment records for tax and accounting. Your answers on the audit quote page are deleted, and so is their copy on any audit order you placed; the order keeps its price and line items, because we keep payment records for tax and accounting. A poster's confirmed contact address stays on file — it is what a posting is reachable at, and it is the address item 10's notification emails go to. Your permission to post paid tasks, including a revocation if one was ever recorded, stays too: deleting that row would quietly restore an access somebody removed on purpose. Postings you passed on are stored as a keyed hash with no login column, so there is nothing there to find (item 15). And a newsletter subscription is keyed on your email address rather than your account, with its own one-click unsubscribe that hard-deletes it (item 7).
A sign-in or CLI token from before you asked us to delete your account can't claim paid tasks afterwards. While deletion is running it gets "Your account is being deleted, so you can't claim new paid tasks.", and after that "This account has been deleted, so you can't claim new paid tasks."
When we refuse instead of trying. Three cases, and in all of them nothing is deleted and you stay signed in. If your session does not carry your numeric GitHub id, we stop: a login on its own is not proof of who you are, since GitHub names can be given up and taken by someone else, and half an erasure reported as a whole one is worse than none. If another live session is signed in under your login with a different id — which is what a name that has changed hands looks like from here — we stop for the same reason, rather than delete a stranger's rows. And if an erasure for your account is already running, the second request waits rather than racing the first. Sign in again, or try again in a few minutes.
If any part of it fails, the whole thing reports a failure and leaves you signed in, so you can try again. We would rather tell you it did not finish than sign you out of the only control that can finish it.
Two limits worth stating plainly. Sessions are matched on your login exactly as it is spelled, so if you renamed your GitHub account by changing only the letter case and a
terminalhire linksession predates that rename, it can outlive the erasure; runterminalhire logouton that machine, or let it expire. And a claim of yours that we release on the way past is not announced to the parts of the product that track whether a posting is still being worked on, so a poster may see it as live for a while. Neither touches what is deleted; both are about how quickly the rest of the system notices.Ordering a code audit. When you order a code audit, a developer working for Staqs reads your repository to do it. Our developers may use AI tools to help them, only under settings that do not allow your code to be used to train AI models. We delete our copies of your code within 7 days of delivering the report, and keep the report. When your order is paid, we email a confirmation to the address you gave at checkout, and our team gets a notice of the order that includes that address. Our email processor (Resend) delivers both. The answers you give on the quote page (which units to focus on, your question, context, what worries you) are stored with the quote and the order, sent to the reviewer with the order, and deleted with your account.
Your human audit report contains the review, findings, quoted code lines and codebase write-up. We keep the report after deleting our copies of the repository. A full refund hides the report from your audit page but does not delete it. We delete the report when you delete your account. To ask us to delete a report without deleting your account, email eric@staqs.io. Payment records are kept for tax and accounting. We use Resend to email your checkout address when the report is ready. That email names your repository and the reviewed commit and links to your audit page.
Agent audit. If you buy an agent audit on the audit quote page, our AI agent reads your repository at the commit we quoted: every application-code file the quote counted. It never reads .env files, other secret or key files, or symlinks. We send those files to Anthropic's API, which runs the Claude Sonnet model under Anthropic's commercial terms; those terms do not allow your code to be used to train models. We do not keep the contents of the files the agent read. We keep the findings, including the lines they quote, and the paths of any files it could not read, until you delete your account. The agent audit runs only after you pay for it. An AI agent does it, not a person; the human audit is done by an expert developer.
For an audit quote request, we keep the repository, commit, detected areas and your self-reported booking preference with your account. When you delete your account, we delete any audit request you have not paid for. An order you paid for, or one whose payment page is still open, stays as a payment record, and we delete your booking preference from it. Saving a card stores it with Stripe, not with us, and does not pay for the audit; payment requires your later acceptance of a price. If you open the booking calendar, Google Calendar (part of Google Workspace) loads only after your click. When you book, Google receives your name, email and chosen time under Google’s own terms. We do not send Google your repository name, detected areas or credentials. A booking marked in TerminalHire is your own report, not a confirmation from Google. When you submit the request, our team receives your repository, commit, confirmed contact email and booking preference through Resend.
The surfaces inherit the rules
- The MCP server's match tools answer from the local cache with zero network calls. Claim preview uses governed public reads; claim record writes only the local ledger and cannot start, push, submit, or open a PR. If you configured an Anthropic API key, record applies the same semantic policy-doc reader as the CLI. Message content and consent actions remain excluded (details).
- The editor extension is a read-only renderer of the local cache with no network access at all (details).
- The spinner/statusline surfaces in Claude Code render content computed from a profile that never leaves your machine; supply comes from the public index, not from your data.
- The website counts page views with Vercel Web Analytics — cookieless and anonymous, aggregate numbers only. Your profile never touches the site, so there is nothing for a page view to be tied to.
- The website also records product analytics with PostHog: named steps on the
two paths through the site — posting work, and claiming it — so we can see where
people get stuck. What each event carries is a step name, a step number, and a
handful of tags (which refusal a publish hit, whether a posting is paid, whether a
claim was new). Every event also says which of our deployments recorded it
(
production,previewordevelopment), and every product-analytics event says which of our own surfaces it came from —web,cli,mcporworker. (Error reports carry the deployment but not always the surface, because the handler that catches an unexpected error does not always know which door the request came through.) Both describe our software, not you. While you are signed in, events are tied to your public GitHub login; signed out, each event gets a throwaway id and no profile is built. We never send an email address, a token, a session id, repository contents, a patch, or the text of a posting, and PostHog is loaded with autocapture off — so it records the steps we named, not every click you make. The homepage also sets one cookie,th_hp, holding only the letteraorb, so you keep seeing the same version of the page for 30 days; that letter is sent with the posting steps recorded on the homepage, on/auditand on a project's audit page; nothing else is stored under it, and it identifies nobody. Your locally-matched profile never touches the site, and nothing here changes that. - When we read a poster's repository to scope a posting, we record how big it was: the number of files, bytes and lines in the repository and in what the spec selected, the repository's numeric GitHub id, and whether we downloaded it or reused a copy we already had. When a repository is too large for us to read, or the read runs out of time, we record that too: which limit it hit and the archive size GitHub reported. Counts only — never a file name, a path or a line of its contents. We use it to learn how large repositories typically are, which is what reading them costs.
- To read a repository we download a copy of it, at the commit being scoped, to our server's temporary disk. It is reused for scopes of the same commit for at most an hour after it was downloaded, and never after that; an expired copy is deleted the next time that server downloads a repository, or when the server is recycled, whichever comes first. It is never sent anywhere else, and your GitHub App access is checked again before every read.
- The website also sends PostHog masked session recordings, error reports and
server logs. A recording captures the structure of a page and your interactions
with it — clicks, scrolling, navigation — and every piece of text and every form
field is replaced with a placeholder in your browser before the recording leaves it.
A recording can show that you stopped on the payment step and clicked the same button
three times; it cannot show your email address, your posting text, or anything you
typed. It does carry the address of each page, as the analytics events from your
browser do, and some addresses name a public repository (
/projects/<owner>/<repo>). Before it is sent, the token is taken out of every share, claim or draft link (the part after/c/,/t/,/dashboard/work/or/poster/confirm/). We record campaign source, medium, and campaign names from links you follow to understand how people find TerminalHire. Error reports carry the error type, a stack trace, and the message with addresses and token-shaped strings redacted first. Server logs are lines our own code writes about what it did — never request bodies.
The point
A credential you can trust has to be derived from real work and verifiable — and a matcher you can trust has to prove it isn't quietly a data business. Keeping the profile on your machine is what makes both claims checkable rather than promised.