商业产业 Hacker News Agent 2026-07-30T14:18 正文 ok 2234 words

🔶 Git worktree 并不是 Coding Agent 的隔离边界

英文标题:🔶 Git worktrees are not an isolation boundary for coding agents

文章指出,为并行 Coding Agent 分配独立 Git worktree,并不能形成真正的隔离边界:各 worktree 仍共享同一个 `.git` 中的配置、引用、stash 和 hooks。恶意或失控的 Agent 无需利用 Git 漏洞,即可安装会在宿主机执行的 hook、改写用户提交邮箱、弹出其他 Agent 的 stash,或影响共享分支引用。实测显示,相比 worktree,本地 `git clone` 或 `git clone --shared` 的实际额外磁盘成本主要同样是工作目录,约为 59 MB,耗时不足 1 秒。作者建议,人类手动操作的场景可使用 worktree;对无协调、可自主执行命令的 Agent,则应使用隔离 refs、配置、stash 和 hooks 的 clone。
打开原文相关 6 · 机会 1
media117.6 KB

中文

给一个 Coding Agent 分配它自己的 git worktree——这正是大多数并行运行 Agent 的工具采用的方式——该 Agent 就可以安装一个 hook,使其在下一次你于真实仓库中提交时在你的机器上运行。它可以改写你自己提交所归属的邮箱。它可以把另一个 Agent 的 stash 弹出到自己的工作目录中。

这些都不需要漏洞或罕见命令。worktree 从来都不是一道边界。它只是连接到同一个 .git 的第二个工作目录,而所有重要内容都存在于这个 .git 中。

worktree 在这里成为默认选择,是因为它看起来显而易见。一个命令,不复制历史记录,大约一秒钟即可获得第二个 checkout。其卖点是以几乎为零的成本实现隔离,但这两点最终都不对:隔离程度远比“隔离的 worktree”这一说法暗示的更薄弱,而正确实现隔离的成本其实相同。本文较早版本中,我对成本部分的判断是错误的,直到我进行了测量。

链接 worktree 实际共享了什么

在链接 worktree 内,.git 不是一个目录,而是一个文件:

$ cat ../my-worktree/.git

gitdir: /path/to/repo/.git/worktrees/my-worktree

一切都由这个路径决定。Git 将仓库状态分为每个 worktree 独有的状态和公共状态,并且会告诉你二者分别是什么:

$ git rev-parse --git-dir          # per-worktree

/path/to/repo/.git/worktrees/my-worktree

$ git rev-parse --git-common-dir   # shared with the parent and every sibling

每个 worktree 独有的状态很少:HEAD、index、ORIG_HEAD,以及少量 bisect 和 rebase 文件。其他所有内容都会通过 --git-common-dir 回溯到原始仓库:

  • 对象存储(.git/objects),这是 worktree 的核心目的,并且可以安全共享。
  • 引用和分支(refs/heads、packed-refs)。所有 worktree 共用一个命名空间。
  • 配置(.git/config)。
  • stash(refs/stash)。
  • Hooks(.git/hooks),共享目录由此变成任意代码执行入口。

因此,隔离确实存在,但范围很窄:你的工作目录、HEAD 和 index。它们很有用,但不是一道边界。一道边界意味着,受限于一个 worktree 的进程不能访问其外部状态;而第二个列表就是它可以访问外部状态的方式目录。“隔离的 worktree”一词在工具说明和建议讨论中被频繁使用,却从未说明究竟指的是两个列表中的哪一个。

这会让 Agent 能做什么

先说最糟糕的情况。每个代码块都会创建自己的仓库,因此你可以将它们粘贴到同一个空目录中,并按任意顺序运行。以下所有输出都是真实输出,采集自 git 2.50.1。

在你的宿主机上执行代码

.git/hooks 位于公共目录中。因此,从 worktree 内安装的 hook 实际会安装到父仓库中;下次你在父仓库运行相应的触发命令时,它将以你的身份运行。

git init -q --initial-branch=main hook-demo

git commit -q --allow-empty -m init

git worktree add -q ../hook-agent agent-work

# The "isolated" worktree writes a hook into the parent's .git

cat > "$(git rev-parse --git-common-dir)/hooks/pre-commit" <<'EOF'

echo "*** hook running as $(whoami) in $(pwd) ***"

chmod +x "$(git rev-parse --git-common-dir)/hooks/pre-commit"

# Now you, in your own repository, make an ordinary commit

git commit -q --allow-empty -m "an ordinary commit"

# -> *** hook running as you in /path/to/hook-demo ***

这也是为什么 worktree 不能成为容器化 Agent 的隔离层。向一个容器提供链接 worktree,意味着必须挂载父仓库真实的 .git,因为 worktree 自身的状态就存放在那里。该挂载在宿主机一侧可写的 .git/hooks 会在宿主机上运行,于是容器也就失去了意义。

改写你的提交归属身份

配置是共享的,因此 worktree 内的 git config 会写入父仓库的 .git/config:

git init -q --initial-branch=main id-demo

git commit -q --allow-empty -m init

git worktree add -q ../id-agent agent-work

git config user.email "agent@example.com"

git config --show-origin user.email

# -> file:.git/config  agent@example.com

git commit -q --allow-empty -m "a commit you made yourself"

git log -1 --format='%an <%ae>'

# -> Your Name <agent@example.com>

仔细想想最后两行。那不是 Agent 的提交,而是你在自己的仓库中提交的内容,却带上了其他东西选定的作者信息。

取走另一个 worktree 的 stash

每个仓库只有一个 refs/stash,其行为就像由互不协调的写入者共用的单一栈:

git init -q --initial-branch=main stash-demo

echo original > file.txt && git add file.txt && git commit -q -m "add file"

git worktree add -q ../stash-a branch-a

git worktree add -q ../stash-b branch-b

echo "AGENT A PRECIOUS WORK" > file.txt

git stash -q                 # A parks its work

git stash pop                # B pops it, without ever asking for it

# -> AGENT A PRECIOUS WORK

git stash list               # -> empty

cat file.txt                 # -> original. A's work is now in B's tree.

有一个关键细节,这也是一些流传的复现版本无法工作的原因:file.txt 必须在创建分支之前提交,因此它会在两个 worktree 中被追踪。普通的 git stash 会忽略未追踪文件;所以对于一个全新的文件,它会输出 No local changes to save,不会 stash 任何内容,而 pop 会因 No stash entries found 失败。请使用被追踪的文件,或者使用 git stash -u。

改写同级 worktree 所使用的引用

每个 worktree 都会写入同一个 refs/heads 和同一个对象存储。运行 git gc 的 Agent 实际是对整个仓库运行它,也包括父仓库。强制更新分支、rebase 或使用 git reset --hard 的 Agent,都会修改其他 worktree 解析时依赖的引用;一个 Agent 使某个提交成为孤儿提交后,它可能会在另一个 Agent 不知情的情况下失去引用。这些并非罕见命令,而是某个东西陷入混乱并决定“整理一下”时会使用的命令。

与其他 worktree 在分支名上冲突

共享引用还意味着,git 拒绝让两个 worktree 使用同一个分支:

$ git worktree add ../probe main

Preparing worktree (checking out 'main')

fatal: 'main' is already used by worktree at '/path/to/repo'

和前面的问题相比这并不严重,但这是人们最先撞上的一堵墙,也正因此他们不得不为每个 worktree 生成一个一次性分支,只是为了满足 git 的要求。

那些并不能解决问题的修复方案

每次讨论这个问题时,都会出现两种缓解措施。两者都站不住脚。

每 worktree 配置。extensions.worktreeConfig 默认关闭;即使开启,只有写入者选择使用 git config --worktree 才有帮助。普通的 git config——任何未特意谨慎的工具都会运行的命令——仍然会写入共享文件:

git config extensions.worktreeConfig true    # you opt in, in the parent

git config --worktree user.email scoped@example.com

git -C ../repo config user.email    # -> you@example.com, unaffected

git config user.email leaked@example.com

git -C ../repo config user.email    # -> leaked@example.com

**将 hooks 移出.git。**这存在循环依赖。core.hooksPath 属于配置,而配置是共享的,因此 worktree 可以将其指向任何位置:

cd repo && git config core.hooksPath ../safe-hooks    # your mitigation

mkdir -p evil-hooks && printf '#!/bin/sh\necho pwned\n' > evil-hooks/pre-commit

chmod +x evil-hooks/pre-commit

git config core.hooksPath "$(pwd)/evil-hooks"         # config is shared

cd ../repo && git commit --allow-empty -m "an ordinary commit"

--separate-git-dir 也会以相同方式失败。它只是重新定位 .git,而所有 worktree 依然共享其被重新定位到的位置。

这三种方案都只约束了试图守规矩的写入者,没有一种降低了 worktree能够写入的内容范围。

那就使用 clone。成本相同。

标准反对意见是,每个 Worker 一个 clone 意味着复制历史;但对于本地源而言,实际并非如此。这正是我之前判断错误的部分,所以这里给出数据。

以 git/git 的完整 clone 为基准测量:81,772 个提交、4,828 个被追踪文件、318 MB 的 .git、58 MB 的工作目录。表中每一行都从该本地源创建一个新的 checkout。

说明一下测量方法,因为它会改变答案。磁盘空间以整个目录树的累计增量来衡量。du 每次执行只会计算一个 inode 一次,因此若单独测量硬链接 clone,会将它实际未新增的字节数也算到它头上。

创建一个 checkout 墙钟时间 新增磁盘空间 .git 表观大小 复制的 Packfile
git clone --no-hardlinks 1791 ms 373 MB 315 MB 1
git clone(本地) 982 ms 58.9 MB 318 MB 1(硬链接)
git clone --shared 870 ms 58.9 MB 660 KB 0
git worktree add 826 ms 58.7 MB 4 KB 0

看看最后三行。58.7 至 58.9 MB,826 至 982 ms。从磁盘角度看,它们是同一项操作;这个数字是工作目录 checkout 的成本,几乎与源仓库 58 MB 的工作目录完全一致。无论选择何种机制,你付出的成本都是一份文件副本,没有其他重要成本。

本地 git clone 不会复制对象,而是为其创建硬链接;这就是为什么它的 .git 显示为 318 MB,而真实新增磁盘空间仅为 59 MB:

$ stat -f '%i links=%l' reference/.git/objects/pack/*.pack

$ stat -f '%i links=%l' plain-clone/.git/objects/pack/*.pack

154102172 links=2     # same inode

只有 --no-hardlinks 会承担复制成本,而且没有人会在无意中运行它。如果你因为以为本地 clone 会复制历史而一直避免使用它,事实并非如此。

--shared 更进一步,完全不复制任何对象。它只写入一个文件:

$ cat shared-clone/.git/objects/info/alternates

/path/to/reference/.git/objects

$ find shared-clone/.git/objects -type f | grep -v /info/ | wc -l

因此,与普通 clone 采用硬链接后的 318 MB 相比,它的 .git 仅为 660 KB。它真正带来的特性是与历史规模无关:--shared 对源仓库历史量是 O(1),对 checkout 文件数量是 O(n)。工作目录 checkout 才是主导成本,表格正是如此显示的。

我此前在本文中将其描述为只需“几 KB 和几毫秒”。这对元数据而言是正确的,但对整个操作是错误的。它的成本是几 KB 的 .git 加上一个工作目录,耗时不到一秒。

用相同的 59 MB 成本,你可以获得以下结果:

$ cd plain-clone   && git config user.zzTest x && git -C ../reference config user.zzTest

$ cd shared-clone  && git config user.zzTest x && git -C ../reference config user.zzTest

$ cd worktree      && git config user.zzTest x && git -C ../reference config user.zzTest
状态 Worktree --shared clone
对象存储 共享 共享,通过 alternates
引用 / 分支 共享 隔离
配置 共享 隔离
Stash 共享 隔离
Hooks 共享 隔离
Index 隔离 隔离
HEAD 隔离 隔离

两者都共享了唯一一个复制成本高且可安全共享的部分。clone 还隔离了另外五项;一旦有多个写入者,它们就会立即变成危险点。

--shared 的代价

它借用对象,因此依赖源对象存储持续存在。不要在其下方删除源仓库,也不要在 clone 仍存在时对源仓库运行激进的 git gc,否则可能会清理掉某个 clone 仍在引用的对象。如果之后想解除依赖,可以使用 git clone --dissociate 将借用的对象复制进来。

普通硬链接 clone 在两者中更为健壮,原因值得了解。硬链接会自行保持 inode 存活:即使源仓库删除或重新打包该 packfile,clone 的链接仍会将数据保留下来。--shared 没有这样的保护,因为它持有的是路径而非引用。你放弃的是 O(1) 特性、永久去重能力,以及跨文件系统的能力;一旦工作空间位于另一个卷上,或被 bind mount 到容器中,这些就开始变得重要。

两者并非绝对优劣。它们以不同方式失效,但都能隔离 refs、config、stash 和 hooks。

何时 worktree 才是正确选择

worktree 有意共享一个 .git,对于其设计用途而言,这种共享正是重点:

  • 一个人在两三个分支之间切换。你希望所有位置都能看到分支、stash 和配置。
  • 快速构建或测试另一个分支,同时不影响当前 index。
  • 任何只有你一个写入者,并且你在运行前知道每条命令行为的场景。

即便使用 Agent,在有人审阅每个 diff 的监督下,让一两个 Agent 使用 worktree 和 shell alias 也是没问题的。无法成立的是互不协调的写入者。“共享一个 .git”和“运行多个无需询问即可行动的进程”之间存在直接张力;无论驱动程序多么谨慎,都无法改变共享目录所允许的行为。

因此:人来操作时使用 worktree,其他自动化实体来操作时使用 clone。两者成本相同,因此你实际选择的只是一个困惑进程可能造成多大破坏。


披露:我在开发 Fletch,这是一款运行 Coding Agent 的 macOS 应用,因此我必须在这两种方案中选择一个。基于上述测量结果,它为每个 Agent 使用 git clone --shared。它的 worktree 模式隐藏在开发者标志之后,因为在容器环境下,这意味着需要挂载真实的 .git,而第一个复现示例中的 hook 会在宿主机上运行。

English / 原文

Give a coding agent its own git worktree, which is how most tools for running agents in parallel do it, and that agent can install a hook that runs on your machine the next time you commit in your real repository. It can rewrite the email your own commits are attributed to. It can pop another agent’s stash into its own tree.

None of that needs a bug or an exotic command. A worktree was never a boundary. It is a second working directory attached to one .git, and everything interesting lives in that .git.

Worktrees became the default here because they are the obvious answer. One command, no duplicated history, a second checkout in about a second. The pitch is isolation at nearly zero cost, and both halves of that turn out to be wrong: the isolation is much thinner than the phrase “isolated worktree” suggests, and the cost of doing it properly is the same. I had the cost part wrong in an earlier version of this post, until I measured it.

What a linked worktree actually shares

Inside a linked worktree, .git is not a directory. It is a file:

$ cat ../my-worktree/.git

gitdir: /path/to/repo/.git/worktrees/my-worktree

Everything follows from that path. Git splits repository state into per-worktree state and common state, and it will tell you which is which:

$ git rev-parse --git-dir          # per-worktree

/path/to/repo/.git/worktrees/my-worktree

$ git rev-parse --git-common-dir   # shared with the parent and every sibling

Per-worktree state is a short list: HEAD, the index, ORIG_HEAD, and a few bisect and rebase files. Everything else resolves through --git-common-dir back into the original repository:

  • The object store (.git/objects), which is the point, and is safe to share.
  • Refs and branches (refs/heads, packed-refs). One namespace for every worktree.
  • The config (.git/config).
  • The stash (refs/stash).
  • Hooks (.git/hooks), which is where a shared directory becomes arbitrary code execution.

So the isolation is real, and it is narrow: your working directory, HEAD, and the index. Those are useful. They are not a boundary. A boundary would mean a process confined to the worktree cannot reach state outside it, and the second list is a catalogue of the ways it can. “Isolated worktree” gets used constantly, in tool descriptions and in advice threads, without ever saying which of the two lists is meant.

What that lets an agent do

Worst first. Each block creates its own repository, so you can paste them into one empty directory and run them in any order. Every line of output below is real, captured on git 2.50.1.

Execute code on your host

.git/hooks lives in the common directory. A hook installed from inside a worktree is therefore installed for the parent repository, and it runs as you, in the parent, the next time you run the triggering command.

git init -q --initial-branch=main hook-demo

git commit -q --allow-empty -m init

git worktree add -q ../hook-agent agent-work

# The "isolated" worktree writes a hook into the parent's .git

cat > "$(git rev-parse --git-common-dir)/hooks/pre-commit" <<'EOF'

echo "*** hook running as $(whoami) in $(pwd) ***"

chmod +x "$(git rev-parse --git-common-dir)/hooks/pre-commit"

# Now you, in your own repository, make an ordinary commit

git commit -q --allow-empty -m "an ordinary commit"

# -> *** hook running as you in /path/to/hook-demo ***

This is also why a worktree cannot be the isolation layer for a containerised agent. Handing a container a linked worktree means mounting the parent’s real .git, because that is where the worktree’s own state lives. A writable .git/hooks on the host side of that mount runs on the host, and the container stops meaning anything.

Rewrite who your commits are from

The config is shared, so git config inside a worktree writes the parent’s .git/config:

git init -q --initial-branch=main id-demo

git commit -q --allow-empty -m init

git worktree add -q ../id-agent agent-work

git config user.email "agent@example.com"

git config --show-origin user.email

# -> file:.git/config  agent@example.com

git commit -q --allow-empty -m "a commit you made yourself"

git log -1 --format='%an <%ae>'

# -> Your Name <agent@example.com>

Sit with the last two lines. That is not the agent’s commit. It is yours, in your repository, carrying an author line something else picked.

Take another worktree’s stash

There is one refs/stash per repository, and it behaves the way a single stack shared between uncoordinated writers would:

git init -q --initial-branch=main stash-demo

echo original > file.txt && git add file.txt && git commit -q -m "add file"

git worktree add -q ../stash-a branch-a

git worktree add -q ../stash-b branch-b

echo "AGENT A PRECIOUS WORK" > file.txt

git stash -q                 # A parks its work

git stash pop                # B pops it, without ever asking for it

# -> AGENT A PRECIOUS WORK

git stash list               # -> empty

cat file.txt                 # -> original. A's work is now in B's tree.

One detail is load-bearing, and it is why some versions of this repro floating around do not work: file.txt is committed before the branches are created, so it is tracked in both worktrees. Plain git stash ignores untracked files, so on a brand-new file it prints No local changes to save, stashes nothing, and the pop fails with No stash entries found. Use a tracked file, or git stash -u.

Rewrite refs under a sibling

Every worktree writes into one refs/heads and one object store. An agent that runs git gc runs it for the whole repository, parent included. An agent that force-updates a branch, rebases, or reaches for git reset --hard is mutating refs that other worktrees resolve against, and a commit one agent orphans can go unreferenced under another’s feet. These are not exotic commands. They are what something reaches for when it gets confused and decides to tidy up.

Collide on a branch name

Shared refs also mean git refuses the same branch in two worktrees:

$ git worktree add ../probe main

Preparing worktree (checking out 'main')

fatal: 'main' is already used by worktree at '/path/to/repo'

Harmless next to the rest, but it is the first wall people hit, and it is what pushes them into generating a throwaway branch per worktree purely to satisfy git.

The fixes that do not fix it

Two mitigations come up every time this is discussed. Neither holds.

Per-worktree config. extensions.worktreeConfig is off by default, and switching it on only helps if the writer opts into git config --worktree. A plain git config, which is what anything not specifically being careful will run, still writes the shared file:

git config extensions.worktreeConfig true    # you opt in, in the parent

git config --worktree user.email scoped@example.com

git -C ../repo config user.email    # -> you@example.com, unaffected

git config user.email leaked@example.com

git -C ../repo config user.email    # -> leaked@example.com

Moving hooks out of.git. This one is circular. core.hooksPath is config, and config is shared, so the worktree points it wherever it likes:

cd repo && git config core.hooksPath ../safe-hooks    # your mitigation

mkdir -p evil-hooks && printf '#!/bin/sh\necho pwned\n' > evil-hooks/pre-commit

chmod +x evil-hooks/pre-commit

git config core.hooksPath "$(pwd)/evil-hooks"         # config is shared

cd ../repo && git commit --allow-empty -m "an ordinary commit"

--separate-git-dir fails the same way. It relocates .git, and every worktree still shares whatever it was relocated to.

All three constrain a writer that is trying to behave. None of them reduce what a worktree is able to write.

Then use a clone. It costs the same.

The standard objection is that a clone per worker means copying the history, and for a local source that is simply not what happens. This is the part I got wrong before, so here are numbers.

Measured against a full clone of git/git: 81,772 commits, 4,828 tracked files, a 318 MB .git, a 58 MB working tree. Each row creates one new checkout from that local source.

A note on method, because it changes the answer. Disk is measured as a cumulative delta over the whole tree. du counts an inode once per invocation, so measuring a hardlinked clone on its own would credit it with bytes it never actually added.

Creating one checkout Wall time Disk added .git apparent size Packfiles copied
git clone --no-hardlinks 1791 ms 373 MB 315 MB 1
git clone (local) 982 ms 58.9 MB 318 MB 1 (hardlinked)
git clone --shared 870 ms 58.9 MB 660 KB 0
git worktree add 826 ms 58.7 MB 4 KB 0

Look at the bottom three rows. 58.7 to 58.9 MB, 826 to 982 ms. They are the same operation as far as your disk is concerned, and that number is the working-tree checkout, matching the source’s 58 MB tree almost exactly. Whichever mechanism you pick, you are paying for one copy of your files and nothing else that matters.

A local git clone does not copy objects, it hardlinks them, which is why its .git reports 318 MB while adding 59 MB of real disk:

$ stat -f '%i links=%l' reference/.git/objects/pack/*.pack

$ stat -f '%i links=%l' plain-clone/.git/objects/pack/*.pack

154102172 links=2     # same inode

Only --no-hardlinks pays for the copy, and nobody runs that by accident. If you have been avoiding local clones because you assumed they duplicate history, they do not.

--shared goes further and copies nothing at all. It writes one file:

$ cat shared-clone/.git/objects/info/alternates

/path/to/reference/.git/objects

$ find shared-clone/.git/objects -type f | grep -v /info/ | wc -l

Hence a 660 KB .git against the plain clone’s hardlinked 318 MB. The real property that buys you is independence from history size: --shared is O(1) in how much history the source has and O(n) in how many files you check out. The checkout dominates, which is what the table shows.

I have described this before, in this post, as costing “kilobytes and milliseconds.” That is true of the metadata and false of the operation. It costs kilobytes of .git plus one working tree, in under a second.

For that same 59 MB, here is what you get:

$ cd plain-clone   && git config user.zzTest x && git -C ../reference config user.zzTest

$ cd shared-clone  && git config user.zzTest x && git -C ../reference config user.zzTest

$ cd worktree      && git config user.zzTest x && git -C ../reference config user.zzTest
State Worktree --shared clone
Object store shared shared, via alternates
Refs / branches shared isolated
Config shared isolated
Stash shared isolated
Hooks shared isolated
Index isolated isolated
HEAD isolated isolated

Both share the one thing that is expensive to copy and safe to share. The clone also isolates the five that turn into footguns as soon as there is more than one writer.

What --shared costs you

It borrows objects, so it depends on the source object store staying where it is. Do not delete the source underneath it, and do not run an aggressive git gc on the source while clones are live, or you can prune objects a clone still references. git clone --dissociate copies the borrowed objects in and cuts the dependency if you want out later.

A plain hardlinked clone is the more robust of the two, and the reason is worth knowing. A hardlink keeps the inode alive by itself: if the source deletes or repacks that packfile, the clone’s link holds the data open anyway. --shared has no such protection, because it holds a path rather than a reference. What you give up is the O(1) property, permanent deduplication, and the ability to cross filesystems, which starts to matter the moment workspaces live on another volume or get bind-mounted into a container.

Neither is strictly better. They fail differently, and both isolate refs, config, stash and hooks.

When a worktree is the right call

Worktrees share a .git deliberately, and for the job they were built for that sharing is the whole point:

  • One person moving between two or three branches. You want branches, stash and config visible everywhere.
  • A quick build or test of another branch without disturbing your index.
  • Anything where you are the only writer and you know what each command will do before you run it.

Even with agents, one or two of them supervised by someone reading each diff is fine on a worktree and a shell alias. What does not survive is uncoordinated writers. “Share a .git” and “run several processes that act without asking” are in direct tension, and no amount of care in the driving program changes what the shared directory permits.

So: worktree when a person is driving, clone when something else is. The cost is the same either way, which means the only thing you are really choosing is how much damage a confused process can do.


Disclosure: I buildFletch, a macOS app that runs coding agents, so I had to pick one of these. It gives each agent a git clone --shared, for the reasons measured above. Its worktree mode is behind a developer flag, because under a container it would mean mounting the real .git, and the hook in the first repro would run on the host.