Missing tokens no longer abort the run. The credential is still created and
still attached to every node that needs it, holding a placeholder — so the
workflows open without a missing-credential warning and nothing is left
half-wired. They just cannot run until a real token is supplied.
The placeholder is labelled in both places someone will look: the credential
name becomes "Anthropic (seed, fake key)", which is what shows in the
credential list and on every node, and the stored key is
SEEDED-FAKE-KEY-not-a-real-credential, so a 401 in a log explains itself rather
than raising a question about which key was used. The name is resolved once and
used for the row, the cached name on each node, and the report, so those three
cannot disagree.
The report ends with what to do about it: which token each placeholder needs,
where to generate it, and every place it can be set, each marked exists or
create it. Re-running with the token set replaces the credential in place and
keeps its id, so every node keeps pointing at it and the label drops away —
verified by seeding placeholders and then re-seeding with real tokens.
SEED_PLACEHOLDER_CREDENTIALS=1 forces placeholders even where a real token is
available, for producing a database with no real keys in it or for seeing the
estate as it looks before anything is configured.
`pnpm seed:account` builds n8n-core and runs the seed in one command, so the
build prerequisite stops being something you have to know.
Tokens now resolve from the environment or from a dotenv file this repo already
uses for local secrets — packages/cli/.env, packages/@n8n/instance-ai/.env, or a
root .env, in that order, with the shell always winning. Only the two token
variables are taken from those files, so nothing else in them leaks into the
process. A missing token is named with the credential it is for, where to
generate it, and every place it can be set, each marked exists or create it. The
run report says which source each token came from and how long it is, never its
value.
Also puts it where an agent will actually look: a section in the root AGENTS.md
saying what the seed contains, when to reach for it, and how it differs from
seedInstance.mjs. An empty instance is a bad place to test anything that reasons
about a user's work, and until now nothing in the docs an agent loads by default
said a seed existed.
Replaces the SQL seed with one script that writes six kinds of data in
dependency order: data tables, credentials, workflows, execution history,
Instance AI threads, and the activity log. Ten workflows now, two of them
reading the data tables.
SQL could not carry three of those. `execution_data.data` is flatted-encoded
rather than JSON, so a hand-written payload is unreadable. Data-table rows
live in a `data_table_user_<id>` table that n8n creates at runtime, so the DDL
has to be issued before the rows exist. And credential secrets are encrypted
with the instance key.
On that last point: the key can be pinned across a team, and n8n's ciphertext
is portable when it is — the salt travels inside the OpenSSL envelope, so any
instance with the same key string decrypts it. But a key shared next to its
ciphertext gives no confidentiality; committing an encrypted token beside the
key that opens it is a slower way of committing the token. And pinning is a
hard cutover, since n8n refuses to start when the config file and the env var
disagree. So no key is pinned: the script reads the key this machine already
has, takes the tokens from the environment, and encrypts locally. The script
is what gets shared. Token lengths are reported, never values.
Running the phases in one pass is what keeps the parts honest. Activity
entries cite the execution ids that were actually inserted, threads reference
workflows that exist, and the data-table workflows point at tables that were
really created — none of which survives being hand-maintained across six
tables.
The estate stays deliberately biased: every workflow uses the Anthropic chat
model and touches Linear, and for every job where n8n offers a choice exactly
one node is used everywhere. The Data Table node joins that table with no
alternative to pass over, since there is none. Still no Code node anywhere.
Verified against copies of a real instance database. All 68 nodes resolve to
node types that exist in this tree at the version given. Every execution
decodes through flatted with the run data, per-node statuses and failing node
intact, and both failures stop at the node that threw. Data-table rows read
back with the column types n8n's own DDL would have produced. All 12 thread
messages parse to the shape the agent memory writes. Nine activity entries
cite an execution and none of them dangles; no entry is left unscoped. Three
consecutive runs hold the counts steady with no duplicated credential blocks,
and a build without the activity table skips that phase with a printed note
rather than failing.
Eight small workflows written straight into the database, so a fresh account
has a plausible recent history rather than an empty list. Every workflow uses
the Anthropic chat model and touches Linear, and where n8n offers more than
one node for a job exactly one was chosen and used everywhere — If over
Switch, Filter over If-plus-NoOp, Loop Over Items over a Code loop, and no
Code node at all. The header records every choice and what it was chosen over,
because the consistency is the point: adding a workflow means following that
table rather than picking again.
SQL rather than the public API for two reasons the API cannot cover.
`activity_event` is written from the event bus and has no endpoint, so a
history can only be inserted. And the API stamps timestamps at request time,
which makes every workflow look a second old — the estate is spread over a
fortnight instead, which is what makes an activity feed worth reading.
Credentials are the thing SQL cannot do: their secrets are encrypted with the
instance key. Nodes are seeded unattached, with an optional trailing section
that links whichever Anthropic and Linear credentials it finds. That section
guards on EXISTS — without it a missing credential makes the sub-select NULL
and REPLACE would blank the whole nodes column.
Nothing lands active, no Linear team is chosen, and the one HTTP Request node
posts to example.invalid, so seeding cannot wake or call anything.
The activity section is last and self-contained: on a build without those
migrations its four statements fail with "no such table" and everything above
is already committed.
Verified by running it against copies of a real instance database: 8 workflows
and 8 sharing rows land, every nodes/connections blob round-trips through
JSON, all 54 nodes resolve to node types that exist in this tree at the
version given, the credential-linking section attaches ids without breaking
the JSON, the EXISTS guard leaves nodes intact when no credential is present,
repeated runs stay at 8 workflows and 24 activity entries, and a build with
the table seeds 24 entries with no unscoped rows.