desktop-shell
Skill4 starsChanged 2 months agoMITView on GitHub
Work on the CODM desktop shell — the Tauri v2 host (packages/app/tauri), the react client-side services DI (packages/app/react/src/services — ports + platform services + Container/Token + ServicesProvider), sidecar supervision, or any OS-integration capability (folder picker, notifications, badge, secrets, autostart). Use whenever a task mentions Tauri, the desktop app, sidecars, or an OS capability the react console needs.
- Reads credentials
We split this skill into the rules it gives an agent
5 rules. 0 are common, 2 are distinctive, and 3 are very specific to this project.
0
Common
Most of the 3 edited versions we read keep these.
2
Distinctive
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 293
react → tauri: only through the tauri touchpoints — services//Tauri*Service.ts, services/registry/tauri.ts, services/utils/tauri/.
- Very specific: line 302
UI never branches on the host: If a screen needs "desktop-only" behavior, add a capability to the relevant port (or a new port) and let the …
- Very specific: line 327
Add the [<Cap>Token, <Plat><Cap>Service] pair to the DECLARATIVE record in registry/tauri.ts and registry/browser.ts (class references, …
- Distinctive: line 358
every update invalidates the disk permission: When the grant lapses, macOS is asked from a background sidecar, where it cannot show a …
- Distinctive: line 377
Keep them there, not here: this section is the rule, that one is the runbook.
“Common” is measured: kept by at least 6 in 10 of the 3 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Desktop Shell (Tauri v2)
Flat skill — no per-lang variants. Two artifacts, one contract, two direction rules.
Config is GENERATED, and it lives INSIDE the shell package
The shell's identity/wiring lives in packages/app/tauri/config/ — a flat, INTERNAL config
folder: app.ts (displayName, identifier = keychain service, console wiring — dev port key,
devPath, distSubpath, buildTarget, connectsTo), window.ts (WINDOW presentation + WINDOW_FRAME
size/label), capabilities.ts (the abstract CAPABILITIES keys —
['filePicker','notification','badge','secrets','autostart','hostInfo'], never a Tauri permission
string — plus the CAPABILITY_PERMISSIONS map), and sidecars.ts (the lean manifest). The ONLY
things read from the abstract contract (template.config.ts) are REPO.brand (identity),
REPO.workspaces (roots), and REPO.env (ports/paths) — a literal port/name/path in a config file
that exists in REPO.env is a bug, same rule as the env registry.
Shell-supplied env is DERIVED from the manifest, never hand-listed. config/env.ts types
ShellEnvKey as the REPO.env keys whose consumers include appTauri and maps each to its value
(SHELL_ENV: port examples from the manifest, cloud origin from cloud.ts); forwardedEnv(workspace)
is set algebra over the same relation (daemon gets API_PORT + CODM_CLOUD_URL, gateway gets
CHANNEL_PORT). generate.ts renders it into src-tauri/shell-env.json (committed, drift-gated
by bun desktop:generate --check); build.rs re-emits each pair as
cargo:rustc-env=CODM_SHELL_ENV_<ROLE>_<KEY>; src/shell_env.rs is the only Rust module that reads
them back (env!() compile-time constants; process.env still overrides at runtime — dev keeps the
root .env). Adding appTauri to a key's consumers without a value in SHELL_ENV is a tsc error
by construction. RUNTIME facts (CODM_DATA_DIR, CODM_MIGRATIONS_DIR, CODM_PARENT_PID,
CODM_APP_VERSION, NODE_ENV) stay computed in sidecars/mod.rs — only the running process knows
them. Never edit shell-env.json by hand; never reintroduce a port/URL literal in .rs (the 0.5.1
daemon shipped without CODM_CLOUD_URL and answered 503 CLOUD_UNREACHABLE to every screen because
the supervisor's hand-written env list drifted from the manifest). The release workflows read
VITE_CODM_CLOUD_URL from the committed shell-env.json — there is no CODM_CLOUD_URL repo
variable any more.
Both the abstract keys AND the capability → Tauri permissions[] map live together in
config/capabilities.ts (CAPABILITIES + CAPABILITY_PERMISSIONS) — the config knows what
capabilities exist and how each maps to Tauri's permission grammar. config/generate.ts renders
default.json by mapping CAPABILITIES through CAPABILITY_PERMISSIONS — fail-loud on a
capability key with no mapping.
bun desktop:generate (packages/app/tauri/config/generate.ts) renders two committed outputs:
| Output | Content |
src-tauri/tauri.conf.json | identity, window, devUrl, frontendDist, externalBin, CSP, bundle.resources |
src-tauri/capabilities/default.json | permissions from config/capabilities.ts CAPABILITIES mapped through CAPABILITY_PERMISSIONS |
Drift is a red build: bun desktop:generate --check runs inside test:tooling
(packages/app/tauri/config/generate.test.ts, DSK-01..06 rails — includes a Cargo.toml brand-name
check). config/build-sidecars.ts reads the SAME config/sidecars.ts manifest (binary names, cwds,
entries, build kinds); only host-triple knowledge stays local. Genuine shell decisions (window size
defaults, health-check timing, sidecar:ready/error vocabulary, icons, the data subdir under
app_data_dir()) stay as parameters in the shell — they have no repo-fact source.
DMG install window (config/dmg.ts, rendered into bundle.macOS.dmg): window size, screen
position and the CENTER of each icon (app + Applications alias), in Finder content points. The
background picture src-tauri/dmg/background.png is committed and drawn from the SAME object by
bun desktop:dmg-background (scripts/og/dmg-background.ts — Playwright at 2×, then a pHYs
chunk declaring 144 DPI, or Finder paints it at double size). Rails: DSK-13 (conf ↔ config, file
exists) + scripts/og/dmg-background.test.ts (PNG = 2× windowSize @ 144 DPI, arrow between the
icons). Facts measured on a real volume (2026-08-25): the bundler fixes icon size 128 / label 16
(DMG_FINDER, not configurable); windowSize INCLUDES the title bar, so ~28 pt of the picture's
bottom are hidden — keep nothing important there; and Finder draws icon labels in DARK text over a
light background picture even in dark mode, so one light picture serves both appearances. Moving an
icon = edit config/dmg.ts → bun desktop:generate + bun desktop:dmg-background.
On CI the layout is applied by an AppleScript driving Finder, and the bundler SKIPS it when
CI=true (--skip-jenkins, tauri#592) — the DMG then ships .background/background.png but no
.DS_Store, i.e. the bare window (that is what v0.5.3 shipped). Both release workflows set
TAURI_BUNDLER_DMG_IGNORE_CI=true on the macOS leg; it works because the runner is a LaunchAgent in
the mini's GUI session, and it needs an Automation → Finder grant for the runner's node
(externals/nodeXX/bin/node — the TCC client behind run: steps, NOT Runner.Listener). The
prompt never shows for a bare binary: the first run hangs 2 min, macOS records a denial, and the
entry is toggled on by hand in System Settings; a runner node upgrade changes the path and repeats
the dance (procedure in docs/RELEASE.md). A denied grant is exit 64 from the bundler = red
release, not a silent fallback.
Adding a sidecar = add an entry to config/sidecars.ts (SIDECARS) + bun desktop:generate —
never edit tauri.conf.json/mod.rs literals. Adding a native capability = add the abstract key to
CAPABILITIES AND its permission list to CAPABILITY_PERMISSIONS — both in config/capabilities.ts
— then regenerate. The two must agree — a key with no entry in the map fails the render loudly.
Packaged-app ports — a CANDIDATE list, never a fixed value (2026-08-25/26)
Incident. The 0.5.4 installed build's daemon (API_PORT=3030, SHELL_ENV.API_PORT copied
verbatim from REPO.env.API_PORT.example — the SAME family bun dev uses) died on boot with
EADDRINUSE: Failed to start server. Is port 3030 in use?: port 3030 had been held for 15h by an
unrelated bun run --watch ./src dev server from a sibling project on the same machine. Cascading
symptoms: boot failed for 1 sidecar(s): codm-daemon, the login loopback answered NETWORK_ERROR,
the SSE stream answered Load failed, and the operator saw an icon that produced no usable window.
Decision: the packaged app gets its OWN port family, far from every dev/service port this
repo's stack (or a founder's other projects) is likely to bind, tries several candidates at boot,
and fails LOUD (never silently rebinds to a wrong address) when every one of them is taken.
The contract. config/ports.ts declares PORT_CANDIDATES — an ORDERED list per sidecar
(API_PORT/CHANNEL_PORT), in an unusual range, below the OS ephemeral band (see that file's
docblock for the exact numbers and the reasoning behind each constraint). config/env.ts's
SHELL_ENV carries a ShellEnvEntry per key — { kind: 'candidates', candidates: [...] } for a
port, { kind: 'fixed', value } for a URL/brand name — DECLARED per key, never inferred from
key.endsWith('_PORT'). generate.ts renders shell-env.json with the candidate list as a JSON
ARRAY (not a single string); build.rs reads the JSON's own type (Value::Array vs ::String) to
decide whether to join the elements with , before emitting one cargo:rustc-env, and validates
every element; shell_env.rs::port_candidates splits the CSV back into Vec<u16> at compile-time
constant read.
The CSP authorizes EVERY candidate. Generation time cannot know which one will win at boot, so
connect-src/img-src list all of them — documented in generate.ts next to sidecarPortCandidates,
with the Load failed symptom named explicitly (same failure mode ./cloud.ts documents for a
missing cloud origin: the webview blocks the request before it leaves, with no HTTP status to
distinguish it from a genuinely dead server). Forgetting to regenerate after editing
config/ports.ts reproduces that exact symptom. generate.test.ts DSK-17 gates that every declared
candidate is actually present in the CSP.
The boot-time choice. sidecars::resolve_ports() (src-tauri/src/sidecars/mod.rs), called
ONCE in lib.rs's setup before api::Api is built or any sidecar env is assembled, resolves
BOTH ports up front: process.env (API_PORT/CHANNEL_PORT) still pins one fixed port outright —
how dev/e2e work — otherwise sidecars::lifecycle::resolve_port tries each candidate, in order, via
a real TcpListener::bind-then-release (port_conflict's technique) and keeps the first free one.
Resolving in exactly one place, threaded through to both api::Api::new(api_port, channel_port)
and sidecars::sidecars(..., api_port, channel_port), is load-bearing: two independent candidate
scans could each land on a DIFFERENT free port and put the typed SDK client and the spawned process
on different addresses. A sidecar whose every candidate is occupied comes back as a
PortsExhausted { name, candidates } instead of a port; lib.rs pre-counts it into the
ReadinessGate's total (it will never call boot_sidecar, so it must never make the gate wait
for an arrival that isn't coming) and reports it via sidecars::report_failure with a reason naming
every port that was tried — the same boot-error splash a spawn failure routes to, never the generic
"did not open".
The daemon's own race. The shell's bind-then-release has an inherent gap before the daemon's
own listen() — something else can grab the port in between. packages/api/typescript/src/bootError.ts
(formatBootError) collapses that race, when it happens, into the SAME one-line, port-naming reason
the shell's own port_conflict uses — never Bun's raw multi-frame stack trace, which the persisted
stderr would otherwise hand the boot-error splash verbatim.
The console asks, never assumes. A packaged app can no longer bake VITE_API_URL at build time
— there is no single port to bake. HostInfoService.apiBaseUrl() (packages/app/react/src/services/HostInfoService)
is the ONE new non-diagnostic method on that port (platform() stays diagnostics-only): the Tauri
impl asks the typed host_ports command (src-tauri/src/commands/host_info.rs, tauri::State<ResolvedPorts>
set once alongside resolve_ports()); the browser impl answers null (that host never decides —
router.tsx's module-load configureClient(serviceBaseUrls), the VITE_API_URL default, stands).
ServicesProvider calls it once, BEFORE setContainer reveals the app tree (Outlet lives inside
the provider, so no route loader can fire before this resolves), and re-configureClients with
computeServiceBaseUrls(hostApiBaseUrl) (packages/app/react/src/lib/config.ts — the ONE formula
both the default and the runtime override use) when the host supplies one.
A splash de boot mostra a CAUSA, e oferece a AÇÃO (2026-08-27)
Incidente. Um testador em Windows viu boot-error dizendo apenas "um ou mais serviços não
responderam em 60 segundos". Nos logs: o daemon travou num primeiro boot e o processo travado ficou
VIVO segurando <dataDir>/daemon.lock, então TODA abertura seguinte morreu na hora com
DATA_DIR_LOCKED: Another daemon is already running on this data dir "…" (pid 16580). Stop the other
daemon… — a solução inteira, impressa, e invisível. O operador esperou horas.
Três regras, cada uma o falseador da anterior.
- PULL sozinho não serve para esta janela.
boot-erroré DECLARADA emtauri.conf.json, então seu webview existe — e o script roda — desde o primeiro instante do processo, enquanto a primeira falha só chega até 60s depois. Oinvoke('boot_failures')de carregamento respondia lista vazia e a página nunca perguntava de novo: título, subtítulo e nada.sidecars::applyagora TAMBÉM emitegate::BootFailed(evento tipado,boot-failed), e a página faz as duas coisas — olistencobre a página que já estava de pé, oinvokecobre a que carregou depois do veredito. - Os DOIS streams do sidecar são capturados (
CommandEvent::Stdoutdeixou de cair no_ => {}). Um processo que MORRE grita no stderr; um que TRAVA — o primeiro dominó do incidente — não escreve nada lá, e o que ele imprimiu normalmente é a única prova de até onde chegou.SidecarFailuretraz os últimosSPLASH_TAIL_LINES(30) desse fluxo unificado; o arquivo emlogPathtem a corrida inteira. - Uma causa acionável vira BOTÃO, e a detecção é sobre o erro NOMEADO.
sidecars::remedyvarre a razão + a cauda procurando um token que façaApiErrors::from_str(o enum GERADO do contrato,codm-client-rust) — nuncacontains("DATA_DIR_LOCKED"). Do outro lado da costura,packages/api/typescript/src/bootError.tsimprime umBaseErrorcomo<CODE>: <message>, uma linha, código primeiro, sem stack. UmRemedycruza para o webview como enum fechado (specta) e a página só despacha por variante. O que o botão executa REUSA o sweep (sidecars::reaper,commands::release_data_dir_lock): morre quem tem o caminho de executável exatamente igual ao que este shell spawna, nunca "quem está na porta" nem o pid que a mensagem cita. Sweep vazio é resposta legítima — a tela diz que o dono do lock não é nosso, em vez de reiniciar na mesma tela.
A página é HTML puro em packages/app/react/public/ (sem bundler, sem i18n do console — pt-BR
inteiro, e é assim que ela se mantém consistente). Como ela não importa commands/bindings.ts, o
nome do evento e o valor do Remedy são literais lá: o teste the_splash_page_speaks_the_same_names_this_module_emits
(gate.rs) é o trilho que impede um dos lados de mudar sozinho.
Mental model
The product is the react console; the shell is plumbing. packages/app/tauri may
only ever do three jobs:
- Serve the console — dev via
build.devUrl(http://localhost:5173/app/), prod viabuild.frontendDist(packages/app/react/dist/client, produced bynx run app-react:build-spa;CODM_DESKTOP=trueflips vite to base/+ SPA shell + no nitro). - Supervise sidecars —
bundle.externalBin=codm-daemon(TS,bun build --compile) +codm-gateway(Go).src-tauri/src/lib.rsspawns both on boot and health-checks: daemonGET :3030/session, gatewayGET :3032/api/openapi.json; emitssidecar:ready/sidecar:errorto the webview. Binaries:bun x nx run app-tauri:sidecars(suffix = host target triple). - Back the tauri platform services — keychain
secret_get/set/deletecommands and any future OS capability the contract grows. Custom commands are typed end-to-end (see "Typed commands" below) — no stringlyinvoke.
Typed commands (native calls are typed end-to-end)
There is no stringly invoke<T>(command: string) — that touchpoint is retired. The react
console reaches the host through two typed channels, both owned by @codm/app-tauri:
- Custom Rust commands → tauri-specta. The shell's own
#[tauri::command]s (secret_get/set/delete) are annotated#[specta::specta], collected in atauri_specta::Builderinsrc-tauri/src/lib.rs, and exported as typed TS bindings topackages/app/tauri/bindings.tsby an export test that runs oncargo test(committed + drift-checked). The name, args, and return type all flow from the Rust — one source of truth.TauriSecretsServiceimports{ commands } from '@codm/app-tauri/commands'and callscommands.secretGet(key)— no hand-typedinvoke<string|null>('secret_get',…). Fallback (if specta v2 / tauri-specta v2 version-compat blocks the Rust build): a hand-written typedcommands.tsin@codm/app-tauriwith the same shape — react importscommands.secretGetidentically, types hand-synced. This build did NOT need the fallback. Plugin / core capabilities → typed npm APIs directly. Each
Tauri<Cap>Serviceimports the typed@tauri-apps/plugin-*/@tauri-apps/api/*API — no re-wrapping façade:TauriFilePickerService→import { open } from '@tauri-apps/plugin-dialog'TauriNotificationService→@tauri-apps/plugin-notificationTauriAutostartService→@tauri-apps/plugin-autostartTauriBadgeService→getCurrentWindow().setBadgeCount()from@tauri-apps/api/windowTauriSecretsService→@codm/app-tauri/commands(channel 1)
@tauri-apps/*imports are lint-allowed ONLY insideTauri*Service.ts(theno-restricted-importsallowlist). Invariant:@tauri-apps/*must never resolve into the react main/browser chunk — only the code-split tauri chunk (the registry seam guarantees this; the build gate re-checks it).
Upstream-ready blueprint: this is the standard for the desktop-shell pattern.
template-fullstackhas nopackages/app/tauriyet — when it grows a desktop shell, mirror this shape (specta for custom commands, typed npm APIs for plugins, abstract capability keys in the contract, the capability→permission map + bindings owned by the shell package, generate.ts importing the map).
Client-side services (react → host) — ports + services + decorator-free DI
The react console NEVER knows the tauri surface. packages/app/react/src/services/ is a
small DI container (the frontend analogue of the backend's per-context registry.ts →
InstanceRegistry → child container, minus tsyringe/decorators — reflect-metadata is
friction under Vite and a code-split hazard) organized as ports → platform services →
declarative per-env registry → composition-root injection:
services/
├── core/
│ ├── token.ts # Token<T> = { key: symbol; _t?: T }; token(desc) mints one
│ └── container.ts # Container: #bindings + #cache; load(bindings) / resolve — SINGLETON default,
│ │ # RECURSIVE static deps, `new` lives ONLY here, throws naming the token (unbound + cycle).
│ │ # Exports type Ctor + type Bindings = readonly [Token, Class][]
├── tokens.ts # one token per port: FilePickerToken, NotificationToken, …
├── <Cap>Service/ # colocated per capability:
│ ├── <Cap>Service.ts # the PORT (pure-type interface)
│ ├── Tauri<Cap>Service.ts # tauri impl — the ONLY concrete-class home (+ browser)
│ └── Browser<Cap>Service.ts # browser impl — HONEST degradation
├── registry/ # DECLARATIVE `[Token, Class]` records, ZERO `new` (code-split lives here):
│ ├── browser.ts # default export: Bindings of Browser*Service class references
│ ├── tauri.ts # default export: Bindings of Tauri*Service class references
│ ├── test.ts # default export: Bindings of Fake*Service + the Fake classes (backend `mock`-env analogue)
│ └── index.ts # Environment, ENVIRONMENTS (import('./browser'|'./tauri') → default), detectEnvironment
├── utils/tauri/ # isTauri.ts (env probe) — the stringly invoke.ts is RETIRED;
│ │ # typed calls go through @codm/app-tauri/commands + @tauri-apps/*
├── providers/
│ └── ServicesProvider.tsx # owns the Container; detect → dynamic-import env record → container.load → context (splash while loading)
├── hooks/index.ts # useService(Token) + typed hooks (useFilePicker, useNotification, …)
└── index.ts # public surface: ServicesProvider, hooks, tokens, port types, Bindings/Ctor- Ports: each
<Cap>Service/<Cap>Service.tsholds pure types only — no platform SDK, no react. The ports are the future@codm/native-contractpackage: an expo app implements the same ports (colocatedExpo<Cap>Service+ aregistry/expo.ts) against identical types; extraction is a verbatim move once a second consumer exists. - Services: one concrete class per port per platform, constructed ONLY by the Container.
A service with a dependency declares
static deps = [OtherToken] as constand the Container resolves + injects it recursively — no factory closures, nonewin the registry. Tauri services call the host through typed channels —@codm/app-tauri/commands(specta bindings) for custom commands,@tauri-apps/plugin-*/@tauri-apps/api/*for plugin/core (see "Typed commands" above); the capability each needs is listed inCAPABILITIESand mapped to permissions inCAPABILITY_PERMISSIONS— both inpackages/app/tauri/config/capabilities.ts(capabilities JSON is generated — never hand-edit it). - DI:
ServicesProvidermounts at the composition root (routes/__root.tsx), callsdetectEnvironment()once, DYNAMIC-imports that env's DECLARATIVE bindings record (the browser entry never fetches the tauri chunk — that async boundary is the code-split), builds aContainer,loads the record, and publishes it on context (splash while loading). Components consume ports viauseService(Token)/ the typed hooks — never an environment or a*Serviceclass. Tests/storybook inject a ready Container (c.load(testBindings)fromregistry/test.tsfakes) through thecontainerprop (seeServicesProvider.test.tsx— DI proof, zero tauri).
Direction rules (non-negotiable)
- tauri → react: build config only (
devUrl/frontendDist+ nxdependsOnapp-react:build-spa). The shell never imports console source. - react → tauri: only through the tauri touchpoints —
services/**/Tauri*Service.ts,services/registry/tauri.ts,services/utils/tauri/.@tauri-apps/*,@codm/app-tauri/commands(orwindow.__TAURI__) anywhere else — including the ports and the browser services — is an eslint error (no-restricted-importsblock in the rooteslint.config.ts). new <Something>Serviceappears NOWHERE by hand — the registry is declarative class references and the Container's genericnew K(...)inresolveis the single construction site. Nevernewa service in a component, hook, the provider, the registry, or a test (seed a fake with a tinyclass Seeded extends Fake…subclass instead).- UI never branches on the host. If a screen needs "desktop-only" behavior, add a
capability to the relevant port (or a new port) and let the UI branch on what the port
REPORTS (
supportsFolderPicker()), degrading honestly in the browser services — neverif (isTauri())inside components.HostInfoService.platform()is diagnostics only.
Transport (interim — reversible)
Console → daemon (:3030, HTTP) → gateway (:3032, proxied via api-ts). This is the
same topology as the web dev stack — smallest possible delta. If/when the desktop
transport pivots (SQLite-WAL / IPC — go-domain branch subject), the only shell changes
are the two readiness URLs + platform service bindings; the console does not move.
Adding a capability (worked shape)
- Declare the port in
services/<Cap>Service/<Cap>Service.ts(typed, minimal, promise-based) and add a token inservices/tokens.ts(token<<Cap>Service>('<Cap>Service')). - Implement
Tauri<Cap>Servicecolocated inservices/<Cap>Service/— import the typed@tauri-apps/plugin-*/@tauri-apps/api/*API directly, or (for a NEW custom command) add#[tauri::command] #[specta::specta]insrc-tauri/src/lib.rs, register it in thetauri_specta::Builder(regenbindings.tsviacargo test), and import{ commands } from '@codm/app-tauri/commands'. Add the abstract key toCAPABILITIESAND its permission list toCAPABILITY_PERMISSIONS— both inpackages/app/tauri/config/capabilities.ts— thenbun desktop:generate(+ plugin inCargo.toml/lib.rsif new). - Implement
Browser<Cap>Servicecolocated inservices/<Cap>Service/— never fake success. - Add the
[<Cap>Token, <Plat><Cap>Service]pair to the DECLARATIVE record inregistry/tauri.tsandregistry/browser.ts(class references, ZEROnew), and a[<Cap>Token, Fake<Cap>Service]pair + theFake<Cap>Serviceclass inregistry/test.ts; expose ause<Cap>()hook inhooks/. - Consume the port in the component that owns the interaction (via the typed hook), branching only on reported capability.
- Prove the flow with a fake bound through a test
Container(c.load(testBindings)+ a one-entry[<Cap>Token, SeededFake]override) passed toServicesProvider container={...}.
Commands
bun desktop:dev # tauri dev (starts app-react:dev via beforeDevCommand)
bun desktop:sidecars # compile both sidecar binaries into src-tauri/binaries/
bun desktop:bundle # production shell build (build-spa + sidecars first)bundle (not build) keeps nx run-many -t build green on machines without a Rust
toolchain. Rust is required for dev/bundle; icons must be generated once
(bun x tauri icon <1024.png>) before the first bundle. See
packages/app/tauri/README.md.
macOS signing — the shell's TCC identity (non-negotiable)
Never ship an ad-hoc build. Everything the daemon spawns (claude, and every zsh/git/gh
under it) is attributed by macOS to the app as responsible process — the TCC log says it
verbatim: responsible={identifier=app.codm.desktop}. So the shell's signature IS the agents'
filesystem permission, and workspaces live under ~/Desktop, a TCC-protected folder.
An ad-hoc signature ("signingIdentity": "-") pins the TCC grant to the binary's cdhash, so
every update invalidates the disk permission. When the grant lapses, macOS is asked from a
background sidecar, where it cannot show a dialog — it then records a denial
(Service kTCCServiceSystemPolicyAllFiles does not allow prompting; recording denied) and the
whole spawned tree loses the workspace. Measured on v0.2.0 (2026-08-07): ~640 kernel denials
System Policy: … deny(1) file-read-data /Users/work/Desktop/…, agents crashing with
provider exited with code 1 (EPERM) — Bun dies at startup when it cannot read its own cwd.
Therefore bundle.macOS.signingIdentity carries the real Developer ID
(Developer ID Application: BK COMPANY LTDA (V4F6T68S5B)), and both release workflows pass
APPLE_CERTIFICATE / APPLE_CERTIFICATE_PASSWORD / APPLE_ID / APPLE_PASSWORD /
APPLE_TEAM_ID. A missing secret now fails the build loudly instead of silently shipping ad-hoc.
TAURI_SIGNING_PRIVATE_KEY is a different thing entirely — minisign, for the updater manifest.
Entitlements stay as they are: a Bun single-file executable was measured running fine under
hardened runtime with only com.apple.security.cs.disable-library-validation. No JIT entitlement
needed.
Procedures — issuing a certificate, recovering on a new machine, and re-granting Full Disk Access
after a cdhash change — live in docs/RELEASE.md, section "A assinatura Apple (Developer ID)".
Keep them there, not here: this section is the rule, that one is the runbook.
Diagnosing a suspected recurrence (log is a zsh builtin — the absolute path is required, or the
query silently returns nothing):
/usr/bin/log show --last 30m --predicate 'eventMessage CONTAINS "deny"' --info --debug \
| grep "System Policy"Other versions3 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 3
- btriapitsyn/openchamber★ 12ksame name
- openchamber/openchamber★ 12ksame name
- jlu-lujing/TaskHunter★ 4same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository45 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- agentSkill
- bounded-contextSkill
- clean-branchSkill
- commitSkill
- componentSkill
- controllerSkill
- db-modellingSkill
- ddd-modelingSkill
- ddd-specSkill
- design-systemSkill
- e2eSkill
- entitySkill
- enumSkill
- errorsSkill
- eventSkill
- formSkill
- grill-with-docsSkill
- handlerSkill
- handoffSkill
- middlewareSkill
- migrateSkill
- prdSkill
- primitiveSkill
- projectionSkill
- projectorSkill
- querySkill
- repositorySkill
- reviewSkill
- routeSkill
- saas-researchSkill
- schedulerSkill
- schemaSkill
- sdkSkill
- serviceSkill
- spec-reviewSkill
- storeSkill
- storybookSkill
- task-breakdownSkill
- testSkill
- trace-analysisSkill
- ui-compositionSkill
- usecaseSkill
- user-storiesSkill
- value-objectSkill
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

