Post a paid task
You can start with a repository, a spec, a conversation, or a rough idea. Save the project context first, or go straight to a task on an existing repository. A posting turns the task you are ready to hand off into something a developer can find, claim, and finish — without you handing over repository access to a stranger first.
This is the other half of claims. That page is written for the developer doing the work; this one is for the person paying.
Who can post
Anyone signed in. Posting is tied to your GitHub account, so there is no separate login, no company profile to fill in, and nothing to apply for. The one account that cannot post is one we have revoked, and that is not reversible from inside the product.
Sign in on the web at /dashboard and open the posted tab.
Three things gate the moment you publish, not saving project context. Publishing refuses by name until each is in place:
- the TerminalHire Poster GitHub App installed on the repository you are posting about — that is the App's name on GitHub; look for it under that name
- a saved card
- a confirmed contact email
During setup, sending the email link keeps you on the email step until you confirm. Leave that tab open: it checks automatically and moves to the next unfinished step. The email confirmation page also has a Continue setup link. If your mail browser isn't signed in, sign in with the GitHub account you started with to resume setup.
You can skip a step for now and return to it by choosing it in the setup checklist. Any unfinished step is available there, including Payment details. Skipping does not complete a step: a saved card, confirmed email and repository access are still required before publishing.
You can ask before you draft anything: publish_requirements names whichever of the
three is still missing, and changes nothing.
Setting up from the terminal
terminalhire setup walks the same checklist without the setup page. It opens your
browser to sign in with GitHub and approve a connector for this terminal, the same
approval an AI tool asks for. Tick act on my behalf there: the card and email steps
need it, and so does publishing from the terminal. Setup then opens the GitHub App
install, opens the payments panel on your dashboard to save a card (the card never passes
through the terminal, and saving it charges nothing), and asks for your email address and mails a confirmation
link to it. Each step turns ✓ when the server sees it done. If setup cannot read the
checklist, the rows show ! and it does not say you are ready.
- If the GitHub App covers more than one of your repositories, name the one you post
from:
terminalhire setup --repo owner/name. - The email step needs an interactive terminal. Confirmation mails are limited per account, and a refusal says how long to wait.
- The email step is for first-time setup. Once you have a confirmed address, change it in the browser, under Settings on your dashboard.
Run it again to pick up where you stopped, or terminalhire setup --status to see the
checklist. terminalhire setup --reconnect approves a new connector; the old one keeps
working until you remove it under Settings → Agent connections.
The connector is stored encrypted on your machine. If Claude Code and the
terminalhire command are both installed, setup offers to connect Claude Code, and
asks first. It registers a helper, terminalhire connector-header, that Claude Code
runs when it connects to get the key. The key stays in terminalhire's encrypted store
and is not written into Claude Code's config.
What a terminal can publish
A linked CLI login (terminalhire link) still cannot publish. It lasts thirty
days, and that is too long a key to leave lying around for something that spends
money. What can publish is a connector you approved with permission to act, and
terminalhire setup is one way to create one. With it, terminalhire post submit
saves a draft to your account, and terminalhire post publish <draft> --price <usd>
shows what a claimant would receive and publishes only when you type yes. Both need
an interactive terminal. Publishing checks repository access, your card and your email
again for itself. If the server no longer accepts the stored connector, post submit
removes it, says so, and sends the draft without it.
Without a connector, terminalhire post still drafts, shows, edits and lists drafts
on your machine, and terminalhire post submit sends one to the server and hands
back a browser link to confirm. Accepting finished work and paying stay in the
browser either way, except that a posting set to accept automatically is accepted and
charged 24 hours after a missed decision deadline.
After your first successful publish, setup ends with your live posting and a completed checklist. Choose Open your posting to enter its workspace. If your AI publishes through the connector while the draft is open in your browser, the page catches up automatically when you return. The draft link also takes you to the posting it created.
Writing the posting
Starting with an idea, a spec, or code held by a builder
Start with Project context. You can save a project before you
have a repository, a card, or a complete spec. Your agent can use begin_project
on the poster MCP, ask the returned questions in your conversation, and resume
later with list_projects and get_project.
The interview covers intended users, desired outcome, a concrete workflow, the first deliverable and its exclusions, acceptance evidence, operating constraints, available code and a verification plan. Reuse facts from your spec and authorized code. Proposed or disputed answers stay visible until you review them; questions that affect a later task can remain open.
Choose the repository owner and destination before connecting it. Your agent can
create the repo using its authorized GitHub tools, or you can use GitHub in the
browser. For builder-held code, keep the existing work and agree where it belongs.
Commit the exported PROJECT_CONTEXT.md on the default branch and install the
TerminalHire Poster App on that repository. Confirming checks the actual committed file.
In the browser, the project page walks these steps under Connect a GitHub
repository: create the repository, give TerminalHire access (GitHub brings you
back to the project), then connect it to the project. Organization repositories can
take a moment to appear after you give access.
Your agent then drafts a task with the confirmed project and revision. The context is included in the developer’s task handoff; later project edits do not rewrite an existing task. The normal repository, disclosure, card and contact checks still apply. A context-only repo has a real starting commit, but may have no runtime or tests: the first build task needs a runnable test command and acceptance tests.
“Project context” describes the task you want built. The Contribution Dossier describes a developer’s past work.
Six fields carry the weight:
| Field | What it is for |
|---|---|
| Title | One line, at most 140 characters. |
| Spec | What a developer actually works from. Worth ten minutes. |
| Price | Whole dollars. Below the minimum, the refusal names it. |
| Repo | owner/repo. Mark it private if it is. |
| Stack tags | Up to 12, 40 characters each. They decide who sees this. |
| Claim mode | Open, or approval-only. |
Optional: a link to an existing issue, an effort estimate, and a note about whether you welcome AI-assisted work. The effort estimate reaches the public index as small, medium or large, whatever words you put in.
Reference links. One URL per line in the reference-links box — a Loom walkthrough, a Figma frame, a gist. Your assistant can attach them too, and is asked to prompt you. They appear on the developer's page as links, beside the task, for anyone the posting delivers to. Six at most.
https: only, and that is the whole rule. A link on any other scheme is refused, and
the refusal says which one — "the 2nd reference link must start with https://" — counting
links from the top, so you can fix it rather than hunt for it. Blank lines are ignored and
are not counted.
Links to Loom, YouTube, Vimeo, Figma, GitHub and gists are labelled with what they are. A host we do not recognise still works — it just gets no label, because we have not looked at the page and will not say we have.
They are not embedded and they are not fetched. A reference link is a link.
Set them when you post. They cannot be changed afterwards — the revision record has no column for them, and a field that took your keystrokes and dropped them at the save would be worse than one that says so.
Tags are how developers find you. Matching runs on tag overlap, so a posting
tagged rust and postgres reaches people who work in those; an untagged one reaches
nobody in particular. Write them the way you would write them on a job post.
The spec is the deliverable. A developer decides whether to claim by reading it, and then works from it. A one-line spec produces one-line work, and the price you set does not fix that.
A posting needs a public task description. It is what a developer reads before claiming, and publishing refuses one that lacks any of three sections:
## Outcome
What is different when the work is done.
## Scope
What is included, and what is not.
## Acceptance
How you will judge the result: tests, behaviour, review criteria.
Each needs at least five words or 25 characters, and a placeholder such as TBD or
n/a does not count. ## Deliverable, ## Project context, ## Constraints and
## Open questions are welcome but optional. The headings Goal, Objective,
Scope of work, Acceptance criteria and Definition of done count too.
The composer's box starts with the headings. If one is missing when you publish, nothing is published and the refusal names it; the confirmation page's box is where you fix a draft. Postings published before this rule are not re-checked, but an edit cannot turn a complete description into an incomplete one.
From an assistant, send it as publicBrief with outcome, scope and acceptance
filled in. draft_posting saves a draft without one and says what is still missing.
Work no test suite can judge. For docs, design, config or anything else a test suite
can't judge, choose "No automated tests — I'll review the work myself" when you publish. From
an assistant, send reviewMode: "poster" to draft_posting or confirm_posting. We then
don't look for a test command, and we don't run tests on what is submitted: no workflow of
ours is added to the claim branch and no run is queued. You read the work and decide whether
to accept it. Your posting's decision terms apply as they do to any other posting. You can't
name a test command on the same posting, and the choice can't be changed after publishing;
withdraw and post again instead. Your own repository's workflows may still run on the claim
branch, but we don't record or report them.
Otherwise, a posting needs a test command. When you publish, we read the repository at the
commit being posted and look for the command that runs its tests. If we find one, the
confirmation shows it and asks nothing. If we find none, publishing stops and names the
files we looked in, and you type the command yourself — one line, such as npm test or
pytest. When we also cannot tell the language, pick it from the list beside the field.
If we found a language, or a command that runs only the tests, you cannot replace it:
publishing refuses one you send, and a draft's is dropped. If the command we found also runs
checks such as lint, formatting or typechecking beside the tests, you may name one that runs
only the tests, and verification runs yours instead. From an assistant,
publish_requirements and confirm_posting say when this applies.
The command you name must be plain, because developers see it before they claim: a
build or test tool, or a ./ script, followed by words, flags and relative paths, such
as npm run test:ci. It may start with a cd to a relative path with no ..
in it (cd apps/web && npm test) or with plain settings such as CI=1 npm test, name a
workspace (npm -w @scope/web test), and give a flag its value with =
(--reporter=dot). Publishing and editing refuse anything else, and a draft's is
dropped. Put pipes, quoting or $ references in a package.json script and name the
script. A command that looks like it carries a secret is refused too: a token-like
value, a flag or setting named like a key, token or password (--token=abc,
API_KEY=abc), or any word that is not a short plain word or a path made of them. A short plain word is 24 characters or fewer of lowercase
letters and digits, or letters only, such as a test name, and one is shown wherever it
appears. A file name made of such a stem plus a lowercase extension of up to 8 letters
and digits, such as test_user_authentication.py, is shown too. A secret written in
either form would be shown. Keep secrets in your CI
settings, never in the command.
Before they claim, developers see whether we found the command or you named it, and they see the command itself unless it is one we found that is not plain; that one they read in the repository after claiming. You can change it while the posting is unclaimed. Once someone claims, it is frozen with the rest of the terms.
If we cannot read the repository to look, nothing is published and the draft stays. Try again in a moment; we never treat a failed read as "no command".
An acceptance command, for this task alone. Your assistant can also name a command that
runs the tests for this one task, such as npm -w web test. Send it as acceptanceCommand
to draft_posting, confirm_posting or edit_posting; the web form does not offer it, and
it keeps what your draft named. It sits beside the repository's own test command and never
replaces it, so you can name one even when we found a command. It follows the same rules as
a test command you name. We record it with the posting and show it to developers before they
claim. If the full command fails, we run it on its own, on a fresh copy of the submitted code,
with a five-minute limit, and show its result under the verdict. You can change or
clear it while the posting is unclaimed, and it is frozen once someone claims. A posting you
review yourself can't carry one.
From an assistant, the same two values are testCommand and runtime on
draft_posting, confirm_posting and edit_posting, and publish_requirements says
in advance whether you will need them.
Developers see what the task needs before they claim. The same read records the runtime and its version, the install command, the test command, and any services the repository runs beside the app, such as a database. The posting page shows them under "What you'll need", with the commit they were read at. It also shows when the lockfile is behind the manifest, because then the install cannot use it.
Some things the repository does not say. The confirmation page asks for those, and every answer is optional:
- the runtime version, when no file pins one;
- the image for a service the repository names without one, whose tag, name parts and
registry host labels follow the same short-plain-word rule as a test command, so
postgres:16is accepted and a tag likeTr0ub4dor3is not (a secret written as a short plain word, in the tag or in a host label, would still be shown); - operating-system limits, which no file records, such as "Linux only".
A field you leave blank shows developers "unknown", never nothing. The page only asks what the repository leaves open. An answer to something it already settles is refused, and the repository's own value is what developers see. A runtime version you give is shown to developers; it does not choose the image a verification runs in.
From an assistant, the answers are runtimeVersion, os and serviceImages on
draft_posting, confirm_posting and edit_posting. publish_requirements lists which
are open. A draft is written before the repository is read, so when it is confirmed, any
answer it carries that the repository settles is dropped rather than refused.
If a developer claims the task and cannot set it up, they can hand it back as
setup-failed and name what failed. why_not_claimed counts those hand-backs per posting,
and says which requirement failed once three or more developers have said so.
Drafting from an AI assistant
You do not have to start in the browser. Point any MCP client — Claude Desktop, Claude
Code, Cursor, or a hosted service like Lovable — at
https://terminalhire.com/api/founder/mcp and your assistant can draft a posting from
whatever it already knows about the problem. Nothing to install. Field-by-field setup,
including where to get the token, is under "a host that only accepts a URL" in
the MCP page.
It will ask you which repository the task is in, if it does not already know. That is required, not a nicety: the confirmation page resolves the commit against it. The repository cannot be added there afterwards. An assistant that drafts without one gets a refusal telling it to ask you rather than guess — a guess would point a public posting at someone else's code.
A draft gives claimants the whole repository at one commit. Your assistant cannot ask
for a narrower tier: a draft that names sparse or issue-only is refused, with a
message telling it to leave the tier out.
draft_posting writes a draft and hands back a link; you open the link, sign in,
read the posting rendered, and confirm it. The link grants nothing on its own —
opening it signed out asks you to sign in, and drafts expire after a day.
It can carry your reference links across. If you have already pasted a Loom recording or a
Figma frame into the conversation, your assistant can attach it to the draft rather than
leaving you to paste it again in the browser — and it is told to ask you whether you have
anything of the kind, in case you did not know the option is there. The links are listed
on the confirmation page before you confirm, so you see every link a developer will get.
Same rules as the box: six at most, https: only.
It can also publish that draft, if you let it. A connector you created with permission to
act can call confirm_posting, which publishes a draft using your repository and your
saved card. Once the publishing requirements are met, an unacknowledged full draft
returns an exposure notice with the repository and commit, and waits for acknowledgment.
A sparse draft saved before that rule returns the selected files and waits for confirmed
counts.
Neither preview publishes the posting. For a full draft, the second call has to echo
that notice back with a signed token we issued for that draft, repository and commit,
which goes stale in half an hour; a sparse draft's second call echoes the counts
instead. A read-only connector — the default, and every connector created before this
existed — can draft and read but cannot publish.
The posting tools. Tools marked acts need a connector you gave permission to act. Project context tools are described in MCP.
| Tool | What it does |
|---|---|
preview_work |
reads a public task preview without claiming |
draft_posting |
writes a draft, publishes nothing |
publish_requirements |
which of App, card, email is missing |
list_own_postings |
your postings, never the claimant |
pending_count |
how many wait on a decision from you |
claim_progress |
per posting: attempts, latest check, claim id |
claim_verification |
the newest verification run for a claim |
get_claim_patch |
a held patch and why it was held |
get_check_results |
the sandbox check run for the latest submission |
why_not_claimed |
decline reasons, hidden under three answers |
start_card_setup (acts) |
links to your dashboard to save a card; no charge |
send_contact_confirmation (acts) |
first-time only: mails a link to confirm your email |
confirm_posting (acts) |
publishes a draft you reviewed |
edit_posting (acts) |
changes a published posting of yours |
withdraw_posting (acts) |
takes an open posting down |
request_changes (acts) |
asks the developer for another pass |
rerun_verification (acts) |
runs a claim's tests again at the same commit |
list_own_postings, pending_count and claim_progress name no developer at all.
draft_posting, publish_requirements and edit_posting take decisionWindowHours
(24 to 48, 48 if you leave it out). edit_posting refuses to change it once the
posting has a claim. See Your decision deadline.
Every tool but draft_posting and preview_work needs a connector token in the Authorization header —
you create one per client in the dashboard, it is shown once, and it works until you
revoke it. draft_posting works without one; hand it a token and the draft is
attributed to you and the reply names any publishing requirement you are still missing.
No tool on that server moves money. Accepting finished work, releasing funds, refunds and settlements happen only in the browser, signed in as you. The one exception is a posting that accepts automatically: it is accepted and charged 24 hours after a missed decision deadline (see Your decision deadline). Publishing a posting binds your saved card to it so a claimant knows the task is funded; nothing is charged until the work is accepted.
A posting you confirm this way is recorded as agent-drafted and human-confirmed. It is your posting; the record just says how it was written.
Open or approval-only
An open posting can be claimed by any developer approved for paid tasks — that is an invite list while the beta runs, not a score or a history check — who has also set up payouts. Good for a public repo where a pull request is the whole interaction.
An approval-only posting shows up in the same places, but a developer registers interest and you decide. Nothing about your repo reaches them until you do.
If your repo is private, approval-only is forced — not as a default you can change, but on the server, whatever the form says. For a private repo, approving someone is granting them access, and that should never be something you did by leaving a checkbox alone.
What developers see before you approve
A posting on the public index carries your title, your public summary, an opaque
handle like founder/b-9f2c4e6a8b0d, the price, the tags, the effort estimate, and
the claim mode.
It does not carry your repository, your name, your GitHub login, your email, the issue link, or your spec. That holds whether the repo is public or private, and whatever you choose below.
Your title publishes by default, on a private repo as much as a public one. That is
new. It used to be held back, and the result was a listing that read
Private-repo posting · typescript, react and told a developer nothing about the
work — so nobody could decide whether to claim it without claiming it first. Hiding
the title was never what kept your repo private; the fields in the paragraph above
are.
If a particular title says more than you want a stranger to read, untick show this
title on the open listing in the composer. That posting falls back to a
stand-in built from your tags — Undisclosed posting · rust, postgres — so a
developer can still tell two of them apart. You can change your mind later, including
while someone is working on it: the setting is on the edit form and it is not frozen
by a live claim.
Decide it before you publish, not after. The tick box is on the confirmation page too, so a posting your agent drafted carries your answer from its first moment. The index is public and cached for a few minutes, and the terminal client keeps its own copy, so a title held back an hour after publishing was still read for that hour. Turning it off later stops the next reader, not the last one.
Your public summary publishes either way. That is the one thing the tick box does not touch. You write it in a box that says it publishes and you approve the exact characters, so it is the piece of prose that was always meant for strangers — which is why there is no version of it to hold back.
Once you approve someone, they get the title and the spec whatever this setting says. How much of the code they get is covered in the next section.
How much of a private repo you hand over
A new posting gives the developer the whole repository. The dashboard composer offers no
other scope, and neither does a draft your assistant writes: a request for issue-only
or sparse is refused, not widened. We retired those two because choosing files by
matching words in the spec does not work on a large repository.
Full. The developer clones the repository through a read-only credential scoped to that one repo. The credential can read its history and every branch. You acknowledge that exposure before publishing.
Postings made before this keep their recorded scope, so two narrower scopes are still live on older postings:
Issue-only. You list the files yourself and can supply their contents. What we ask GitHub is about access and identity — whether our App still covers the repo, whether it is private, and which repository it is — and none of it opens a file. The developer gets the repro, the stack trace, and the confirmed file list with any contents you supplied, as a copy, with no access to the repository.
Sparse. With our App on that repo we read your tree to propose a slice; without it you paste the file list and we propose from that. Nothing is stored until you confirm the counts. We start from the files your issue text points at, then add whatever those files import, so a file arrives with the things it refers to rather than on its own. That widening follows imports wherever they lead: a slice can span several directories and pick up build config from the top of the repository, so read the counts rather than assuming a boundary. It is sent as a copy, still with no access to the repository.
For issue-only and sparse, the screen shows you what is being left out as well as what is going: "42 files, 1,180 excluded." Read that number. A list of included files looks equally reasonable whether it is tight or far too generous, and the excluded count is the only part of the screen that tells you which one you are looking at.
For issue-only and sparse, we scan the files before they go. A clear credential —
a key, a token, a private key block, a .env — stops the hand-off outright and we
send nothing. Two blurrier shapes go a different way: a password inside a connection
string, and a bare assignment that reads like a secret. Most of those turn out to be
fixtures, docs and templates, so we put them in front of you instead, and they travel
only if you tick each one. That hard stop arrives while you are still scoping,
before the posting exists, so a tripped scan never leaves you with a live posting that
can't deliver code; the same scan runs again when you confirm, as the authoritative
second check. Every file that
arrives with content is scanned, even one outside the slice — if you sent its bytes,
we looked at them. The same happens if the scan cannot finish: a scan that did not
complete has not cleared anything, so we treat it as a refusal rather than a pass.
You will see what tripped it and can narrow the slice or fix the file.
For full, there is no scan, and there cannot be one. The code travels from GitHub to the developer through a short-lived read credential and never passes through us, so there are no bytes on our side to check. We will not ship a scanner that gives the feeling of having looked when it has not. Instead you confirm, before the posting exists, that everything committed to that repository is exposed and that rotating it is yours to do. Read that literally: the credential is scoped to the repository rather than to the files we show you, so it reaches history as well — a secret committed once and deleted in a later commit is still in there. No confirmation, no full posting.
Every new posting therefore requires an exposure acknowledgment before publication. There is no file list to scope, so instead the form reads the commit your default branch is on right now and shows it to you in full, followed by the exposure notice, with a box you have to tick before the posting can be created. If that commit cannot be read — usually because the App is not installed on the repository — the form says so and refuses to post. It will not fill in a placeholder: a notice naming a commit nobody identified is not something you can meaningfully agree to.
Connecting your GitHub App
Every posting needs the TerminalHire Poster GitHub App installed on its repository, public or private. You install it once, from the dashboard, and pick the repositories it can see — GitHub asks you, not us.
When GitHub asks, pick Only select repositories and tick the ones you'll post from. GitHub starts on All repositories, and a posting that shares your code can't be published from an install left there. You can add repositories later in the App's settings on GitHub.
The App is how the code gets out and how the patch comes back. It reads contents and pull requests, and reads your Actions runs so we can show you whether the checks passed. It never holds a key of yours: access is a short-lived token we mint per posting and throw away.
Uninstall it whenever you like, from GitHub's settings. Postings that depended on it stop being able to deliver code, and say so on the row rather than failing quietly.
The work comes back as a patch
For a private repo the developer never pushes to your repository. They send a patch, and we apply it on a branch in your repo, on your behalf.
On a whole-repository posting they do hold a credential, and it is worth being plain about it: read-only, scoped to that one repo, and short-lived. It cannot write, which is why the patch is still the only way work comes back. On an older issue-only or sparse posting they are issued no repository credential at all — the files arrive as a copy.
What we check is what the patch does, not which files it names. A change to your CI config, your git internals, an install script, a script you already have or a new one that runs before or after another is refused, and so is a patch we cannot read. You see the refusal rather than a surprise commit. Binary files, such as images, fonts and fixtures, wait for your approval. Programs, executable files, and binary files over 2 MB (3 MB per patch) are refused. A package-lock.json (v2 or v3) or yarn.lock (v1) change is accepted with the package.json change it belongs to, when every new entry comes from the npm registry with its integrity hash. Other lockfiles are refused. A dependency change, a new script, or a manifest change we can read, applies without asking you. That is the default, and it stays on unless you turn that off — Dependency and manifest changes, on the posting page, covering every posting on that repository. With it off, each of those waits for your approval instead. A manifest change we cannot read waits for you either way.
That list is the whole of it. A patch is not held to the files you disclosed, so read the branch before you accept.
Your own CI runs on that branch, because it is your repository and your workflows. When the run finishes, the result lands on the posting row: passed, failed, or still going. That is the evidence you decide on — your tests, not our claim about them.
Two other readings sit beside it. get_check_results reports the sandbox check run for
the latest submission — status, counts, a trimmed log tail. claim_verification reports
the newest verification run for the claim, which is
your suite executed in a fenced container with the network cut. Both are also on the
claim's panel in the dashboard.
Deciding
The posted tab sorts by what is waiting on you. A row marked needs you has a developer blocked on your answer, and the badge opens the panel that answers it.
When work arrives you accept or reject it, with a rating from 1 to 5 and an optional note.
For a paid task, save a card first under Settings → Payments. Saving the card and publishing a posting do not charge it; a saved card is not escrow and does not guarantee a later charge will succeed. The final button reads accept — pay $X. That explicit click charges the posted amount. Only after Stripe confirms the charge does TerminalHire record the public acceptance and reserve the developer's payout.
The developer receives 90% of the posted amount. TerminalHire retains 10% and absorbs Stripe's processing fee. If the card is declined or needs authentication, no acceptance or payout is recorded; replace the card or retry from the same decision panel. A retry on the same card after a decline can change the rating or note. If you replaced the card first, send the rating and note you used before. Most refusals name their cause in the panel, such as a price below the minimum. If your bank asks you to confirm the payment and you don't finish, you can still reject, ask for changes, withdraw, or release the developer's claim: we cancel the unfinished payment first. An unfinished payment is also cancelled automatically after 24 hours. Withdrawing before acceptance needs no refund, because nothing was charged — with one exception: a posting paid for up front, before this was how it worked, is refunded from the dashboard rather than withdrawn.
Rarely, a payment goes through but we can't record the accept automatically. Nothing is accepted or paid to the developer yet. A person on our team reviews it, and we email you, even if you have turned off our other emails.
An accept is public. A reject is not. An accepted decision becomes part of that developer's credential — the thing they can show someone else. A rejection is your private judgment of a person's work, and publishing it would turn claiming a posting into a bet on a stranger's mood. Rejections stay between you and them.
Notes are redacted after 180 days. The decision and the rating stay.
Your decision deadline
When a developer first submits work, you have your posting's decision window to accept, reject or ask for changes. The window is 48 hours unless you set a shorter one when you post, down to 24 hours. A claim keeps the window it was claimed under, and once a posting has a claim its window cannot change. The claim page shows poster decision due with the time, and the developer sees the same line on theirs. Asking for changes stops the clock while the developer works, and the line goes away. Their resubmission starts the window again. Accepting or rejecting ends the clock.
We email you twice before the deadline: halfway through the window, and again when an eighth of it is left. On the 48-hour default that is 24 hours and 6 hours before. The emails follow the same notification settings as the others. The deadline cannot be extended.
If the deadline passes, the claim reads not decided in time, and the developer sees that on their claim and in their notifications. On a posting that accepts automatically (the default, unless you ticked Don't accept automatically on this posting when you published), you then get 24 more hours to accept, reject or ask for changes. After that the work is accepted and your saved card is charged. New work submitted during those 24 hours starts your window again from that submission.
On a posting that doesn't accept automatically, nothing else happens: no work is accepted, no card is charged and the claim stays open. You can still accept, reject or ask for changes afterwards.
How you hear about it
Eight things get you an email: a developer claims your posting, a patch arrives, your checks finish, a pull request opens, a submission is blocked, a patch is held for your approval, a developer asks to resolve the posting, and a developer leaves you a note. The last four share a test — you are the only person who can clear them, and there is no other way you would find out. A ninth notice goes out when a claim nobody started is released, and a tenth if your payment goes through but we can't record the accept automatically. Each email links straight to the row it is about; you sign in as normal, and the link grants nothing on its own.
We send to the contact email you confirmed — publishing needs one, so you have one. If for some reason there is no confirmed address, we fall back to the public email on your GitHub profile, and with neither you get no email. Nothing else changes either way: the posted tab is exactly current whenever you open it.
Turning them off. Three ways, and only the last needs you to sign in:
- Your mail client's own Unsubscribe button stops all of them except the held-payment email.
- Each of the eight carries two links: stop all these emails, and stop only this kind. The release notice is the exception — it has no per-kind switch, so only stop all and the settings page reach it. The held-payment email is not optional: it goes out even if you have stopped all emails, because it is about money we have already taken.
- Settings → posting notifications in the dashboard, where each kind is its own switch — and the only place you can turn them back on.
Most people want to keep "a dev claims your posting". That is the one where someone is waiting on you.
Turning email off never hides anything. The posted tab and the MCP count always show every event as it happens.
You can also read the same state without opening the dashboard: the poster MCP
server's pending_count tool reports how many postings are waiting on you, and
reports counts only. claim_progress goes one step further — how many attempts
have been made on each posting and what your own checks said about the latest one.
It also returns the id of the claim each posting is waiting on, so your editor can
open that claim's checks and patch without asking you for a link.
Neither names the developer. That is a rule, not an oversight: the output of an MCP
tool goes to whichever model your editor runs, and a developer working on your
posting never agreed to that. Both tools are read-only. One tool replies:
request_changes sends the developer a note asking for another pass, and only from a
connector you created with permission to act. It records no decision and leaves the
claim open. Accepting work and paying happen on the dashboard, signed in, and no tool
does either.
If a claim's latest run did not pass, or we could not complete it, rerun_verification
runs the tests again at the same commit, with the same permission to act. The earlier
result stays on record and the new run appears beside it; nothing is relabelled. The
developer on the claim cannot ask for this, and a run that passed is not run
again. In Claude Code, the plugin's /approvals skill walks these tools for you.
Why nobody is taking it
An unclaimed posting used to tell you nothing, and nothing is the one answer you can't act on. It can't tell "the price is too low" apart from "there's no CI in that repo, so I couldn't check my own work" — and only the first is fixed by paying more.
Developers looking at your posting in the CLI can now say why they're passing,
picking one of five fixed answers. The why_not_claimed MCP tool reports what
came back: how many passed on each posting, and the breakdown of their reasons.
It is counts, like everything else here. No developer is named, we store no login against an answer, and the breakdown on a posting stays hidden until at least three people have answered — below that you see the number who passed but not what they said, because one answer in a small group can point at a person. A posting with no declines and no claims is its own answer: nobody is getting far enough to have an opinion.
The posting's own page
Every posting has one, and the title on the posted tab is the way in. The tab is a queue — what needs you, what it costs, what state it is in — and the page is where you go to remember what a posting was about and to act on it.
It holds the task you wrote, everyone who claimed it, the whole history in one stream with each line attributed, the money, and your own rating on the entry it belongs to. The actions live there too: approving a developer, deciding on their work, editing the terms, withdrawing.
Editing. Until someone claims it you can change anything. After that the terms are frozen and you can still raise the price — a developer agreed to what was written, so changing it under them is not something we let you do. Every edit is recorded, and the page shows the history.
Two people waiting. If two developers are claiming one posting, both decisions are on the page, each next to the person it is about. The tab can only show you one.
Saying something to a developer
There is a box on the posting's page. Two buttons, because they mean different things: send note leaves a comment and changes nothing, and ask for changes marks their claim as needing another pass.
Your developer sees it the next time their CLI syncs. There is no email and no push on
their side — the CLI shows them a count and they run terminalhire claim notes to read
it, or ask their agent to. So a reply may take a while, and silence is not being
ignored.
A developer can write to you too, from their terminal. Their note appears on the posting's page, and you are emailed that it arrived, without the text. Each developer can send at most 3 notes per claim and 10 to you in any 24 hours. If a note looks wrong — a pasted secret, text that reads as if a repository wrote it — use Report this note under it. The report is recorded, and we try to email it to our operators; if that email does not go through, reporting the same note again sends it. A report hides nothing and changes nothing on the developer's side by itself.
Reading how the work went
Open a claim and you get the whole run of it: every attempt the developer made, the short commit for each one, what your checks said, and the notes you sent back, in order. Where you used to see only the latest attempt and a count of the rest, you now see what happened in each.
The developer sees the same sequence from their side, including the notes you write when you send feedback or reject something. Write them as if they will be read, because they will be.
Your 1–5 rating shows on your own timeline, on the decision it was part of. It is never averaged and never shown as a figure of its own — a score lifted away from the work it was about is the first step toward a number that follows a person around.
Each attempt is also kept in your repository under refs/th/attempts/, so the code
of an earlier try survives the next submission. These are not branches and not tags:
they will not show up in your branch or tag lists, and they cannot trigger a
workflow. They are there so the history stays inspectable.
Taking a posting down
Withdraw removes a posting from the index. You will find it on the row in the posted tab, and it asks once before doing it.
A live claim no longer blocks it. Closing a posting ends every open claim on it and tells each developer why, rather than leaving them working on something that has gone. Three cases still refuse. A developer has submitted work you have not decided yet — decide that one first, accept or reject with a reason. The work was already accepted, which leaves nothing to withdraw. Or the posting was paid for up front, which goes through a refund instead: that takes it down and returns the money.
A withdrawn posting stops appearing to developers within a few minutes.
There is no delete. Withdraw hides it from developers; the record stays, because decisions attached to it are part of somebody's credential.
A patch is accepted only from the person whose claim it is. They record the claim against their signed-in GitHub account, work in a clone of your repository, and submit from their terminal; it lands on a branch in your repo with their commit authorship. On an approval-only posting nothing is delivered and no patch is accepted until you approve the claimant from the needs you panel.
Explore the work and its project
The Work area has views for Working on, Posted by me, and Find work. Developer accounts keep the dark workspace across these views; poster-only accounts use the light workspace. A task opens with its next action, readable task description, progress, and activity.
Open Projects to explore a project's saved goals, workflow, constraints, sources, and open questions before or after it has a repository. The overview labels proposed and unresolved answers. Choose Edit context to review or correct them. Tasks keep the confirmed project context attached when they were prepared; later project edits do not change an existing task's scope. The project's Work section lists the tasks you have posted from it, with each one's status and the context revision it was posted against. A task posted without the project's context attached is not listed there.
Manage editor and agent connectors in Settings → Agent connections.