# Merging pull requests overnight (/docs/haus/night-shift)



[factory](https://github.com/hausfold/factory) merges the pull requests a filter
you wrote can vouch for, on a cadence, while you are asleep.
[`haus.ai.enable`](/docs/haus/rooms/ai) puts it on your `PATH` with its agent
skill, `factory`, and its README is the manual for the shift itself. This page is
the other half: what a shift asks of the Mac underneath it. factory runs on any
machine and names none of this, so the joins are haus's to write down.

## Granting the lease from the desktop [#granting-the-lease-from-the-desktop]

`factory lease grant 12h` is the whole thing, and the desktop has two doors onto
the same four verbs. The bar's [`factory` pill](/docs/haus/rooms/bar#toggling-pills)
draws what is left of the lease and hands you all four. It is on as soon as
[`haus.ai.enable`](/docs/haus/rooms/ai) is, since that is what puts `factory` on
the machine. To keep the lease and lose the pill:

```nix
haus.bar.items.factory = false;
```

The palette carries the same four as **Merge Lease**, with factory's own reading
of the lease as the prompt above them. Both need `factory` on your `PATH`, which
is [`haus.ai.enable`](/docs/haus/rooms/ai).

## Keeping the runner alive [#keeping-the-runner-alive]

`factory watchdog run` is the loop that passes a shift on a cadence, and launchd
owns it here. [`haus.ai.factory.enable`](/docs/haus/reference/options#ai) comes on
with the AI room and installs the agent behind it.

It is a switch about supervision, never about authority: with no lease the runner
exits in under a fifth of a second and launchd starts it again later, so a machine
that has never run `factory lease grant` has a job that does nothing. What it buys is the small-hours case. The
runner `lease grant` spawns is a detached child of your shell, so a reboot, a
panic or an out-of-memory kill takes it and leaves the lease standing all night
with nobody exercising it. Under launchd that death is a restart, and factory's
pidfile keeps two copies from both passing. Turn it off on a machine you drive
`factory` on by hand, where a runner appearing behind you would be a surprise.

## Keeping the Mac awake [#keeping-the-mac-awake]

`haus.ai.keepAwake` is the switch reached for here, and for a shift it is the
wrong one: it means *while my agents are working*, and the runner passing `factory
shift` on a cadence is a plain process that raises no hold. A quiet night raises
none at all, and a busy one holds only while a fixer lane it opened is mid-turn.
An unattended shift names the lid lever directly, in your own power block:

```nix
haus.power.lidAwake = {
  enable = true;
  while  = "always";
};
```

`always` is a plain hold rather than an agent-shaped one, so two of the dials
[Shaping the hold](/docs/haus/rooms/ai#shaping-the-hold) describes have nothing to
act on: `maxHold` caps a hold that leaked, and with no signal to leak a twelve-hour
night is not cut off at hour eight; `linger` has nothing to linger for.
`requirePower` still applies, so unplugging is still a way to say stop.

<Callout type="warn" title="Nothing on screen says the lid is being held">
  The bar's coffee pill reports the agent-shaped hold, not this one: `always` is a
  root daemon holding macOS's `disablesleep`, and it draws nothing. Your shift's own
  notifications are the only receipt, and they stop when the shift does. The hold
  does not, and lasts until you set the option back and rebuild. If the lid stays
  shut and the fans stay on the morning after, check this first.
</Callout>

## Handing a red branch to an agent [#handing-a-red-branch-to-an-agent]

The [github pill](/docs/haus/rooms/bar)'s **Fix with AI** row is a button over a
command you can call yourself:

```sh
haus-fix-github main ci "$url"
```

Three arguments: what is broken (a branch name, or a pull request number), which
of those it is (`ci` for a red default branch, `checks-red` or `conflicts` for a
pull request), and the URL of the failure. It is on your `PATH` whenever the AI
room is on and installs a client, and the lane it opens is a background one, so
nothing raises a window or steals focus and the receipt is a notification.
Everything else, including the checkout it will and will not find, is the same as
pressing the button, which [When GitHub goes
red](/docs/haus/rooms/ai#when-github-goes-red) covers.

A shift calls it through a shim, because the two argument lists do not line up:
factory appends `<repo> <branch> <run url>` to whatever your policy names, and
neither side carries the verdict. `haus-factory-fixer` is that reorder, on your
`PATH` beside `haus-fix-github`, so the policy names the whole of it:

```json
"fixer": { "command": ["haus-factory-fixer"] }
```

`~/.config/factory/config.json` stays yours and haus writes none of it, which is
why `factory doctor` reporting no `fixer.command` is the reminder that this line
is missing.

<Callout title="It spawns, it does not fix">
  The lane is an ordinary lane running its client interactively, with its own
  permission gate and checkout. Nothing here bounds what it does, so whether a fix
  reaches your machine is still the repo's tooling and your review in the morning.
  `haus rebuild` is machine-wide, and it stays yours.
</Callout>

## The feed it meters against [#the-feed-it-meters-against]

factory's budget gate is optional and reads a file you point it at. Here that file
is `~/.cache/claude-statusline/usage-claude.tsv`, written by the [AI
room](/docs/haus/rooms/ai): Claude Code's statusline stashes your percentages on
every render, and a refresher the bar drives fills the gaps by asking Anthropic
directly.

<Callout type="warn" title="The feed needs a terminal in the picture">
  That refresher polls with the credentials a terminal `claude` renews, which the
  Claude Code macOS app never writes, so a machine where nothing has opened a
  terminal session for most of a day goes stale. factory does not then overspend: it
  reports `budget: unknown` and opens **no fixer lanes at all**, which is a quiet
  night that looks like a calm one. If a morning's log is all `budget unknown`, this
  is why.
</Callout>

## What a night sounds like [#what-a-night-sounds-like]

Two notification sources turn up: `factory` for the shift's own cards, and
`haus.github.fix` for every lane the fixer opens. Both are matched exactly, so a
rule on one leaves the other speaking, and both want [the Notifications
room](/docs/haus/rooms/notifications), which factory sends through directly.
Choosing what a merge may wake you for is the difference between a calmer morning
and a night you stop reading.
