skip to content
all docs

Post work

You have work you want done and money to pay for it. A posting turns that 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.

Sign in on the web at /dashboard and open the postings tab.

Three things gate the moment you publish, not the moment you sign up. Publishing refuses by name until each is in place:

  • the Terminal Hire GitHub App installed on the repository you are posting about
  • a saved card
  • a confirmed contact email

A terminal session cannot post, even a linked one. A CLI session lasts thirty days, and that is too long a key to leave lying around for something that spends money.

Writing the posting

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.
Repo owner/repo. Mark it private if it is.
Stack tags Up to 12. They decide who sees this.
Claim mode Open, or approval-only.

Optional: a link to an existing issue, an effort estimate in your own words, and a note about whether you welcome AI-assisted work.

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.

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 work is in, if it does not already know. That is required, not a nicety: the confirmation page resolves the commit for full access or selects files for a sparse draft. 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.

New drafts default to full repository access; your assistant can request sparse instead. 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. An unacknowledged sparse draft returns the selected files and waits for confirmed counts. Neither preview publishes the posting. A read-only connector — the default, and every connector created before this existed — can draft and read but cannot publish.

The other tools read your own postings back to you: a list, a count of how many are waiting on a decision from you, the progress of claimed work, and why developers passed. All of them need 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.

No tool on that server moves money. Accepting finished work, releasing funds, refunds and settlements happen only in the browser, signed in as you. Publishing a posting binds your saved card to it so a claimant knows the work is funded; nothing is charged until you accept the work yourself.

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 anyone. 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 a separate choice, and it is yours — see the next section.

How much of a private repo you hand over

New postings default to full repository access. You can choose issue-only or sparse instead, per posting. Existing postings keep their recorded scope.

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. Right for a bug you can describe precisely.

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. Right for work you can point at but would rather not hand the repo over for.

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. This is the default, not the only choice.

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.

The full default 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

Private-repo postings need the TerminalHire Founder GitHub App installed on the repo. You install it once, from the dashboard, and pick the repositories it can see — GitHub asks you, not us.

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 full posting they do hold a credential for the repository, 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 issue-only and sparse the developer is issued no credential at all.

What we check is what the patch does, not which files it names. A change to your CI config, your git internals, a lockfile or an install script is refused, and so is any change we cannot read line by line. You see the refusal rather than a surprise commit. A dependency change, 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.

Deciding

The postings 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 paid work, 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 says 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. Withdrawing before acceptance needs no refund because no charge exists.

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.

How you hear about it

Four things get you an email: a developer claims your posting, a patch arrives, the checks finish, and the work is done and waiting on your verdict. 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 public email on your GitHub profile. If your profile does not list one, you get no email — nothing else changes, and the postings tab is still 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 four.
  • Every email carries two links: stop all these emails, and stop only this kind.
  • Settings → posting notifications in the dashboard, where each of the four 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 postings tab and the MCP count always show every event as it happens.

You can also read the same state without opening the dashboard: the founder 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.

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, and there is no tool that sends a reply or records a decision — those happen on the dashboard, signed in.

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 postings 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, scoping the files, 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.

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.

Paying

Terminalhire records the decision. It does not move the money — you pay the developer directly, however you already pay people.

Taking a posting down

Withdraw removes a posting from the index. You will find it on the row in the postings tab, and it asks once before doing it.

You can withdraw a posting nobody has started. Once a developer has claimed it or opened a pull request against it, withdrawing is refused — somebody has already spent their evening on your problem, and deleting the posting out from under them is not an answer. Decide the open claim first: accept it, or reject it with a reason.

A withdrawn posting stops appearing to developers within a few minutes.

What you cannot do yet

Edit a posting. Changing the price or the spec after developers have read it is a fairness problem wearing the costume of a missing button, and we would rather leave the button missing than get the answer wrong. For now: withdraw and post again, which is honest about what happened.

Delete a posting. Withdraw hides it from developers; the record stays, because decisions attached to it are part of somebody's credential.

The gap that used to sit here — receiving a patch from a developer — is closed. A developer's claim is now registered against their verified GitHub identity, and a patch is accepted only from the person whose claim it is: they run terminalhire claim record, read the slice, and submit the patch from their terminal, and 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.