Skip to content

[Bug]: t3 update / t3 service install re-render the service unit and drop every user environment variable, including the documented Bitbucket credentials #12626

Description

@sebastiang

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

apps/server

Steps to reproduce

  1. On macOS, run t3 service install.
  2. Follow docs/user/source-control.md for Bitbucket: add T3CODE_BITBUCKET_EMAIL and T3CODE_BITBUCKET_API_TOKEN to the service's environment. The only place launchd takes environment for this job is EnvironmentVariables in ~/Library/LaunchAgents/com.t3tools.t3code.service.plist, so add them there and launchctl bootout / bootstrap.
  3. Confirm Settings → Source Control shows Bitbucket authenticated.
  4. Run t3 update (or run t3 service install again to "repair" the service, as docs/user/background-service.md suggests).

Expected behavior

Environment the user added to the service survives an update or reinstall, or there is a documented place to put service environment that t3 does not overwrite.

Actual behavior

The plist is re-rendered from a fixed template and the user's entries are gone. Bitbucket reverts to "Available. Set T3CODE_BITBUCKET_EMAIL and T3CODE_BITBUCKET_API_TOKEN on the server". The same happens to T3CODE_PORT / T3CODE_HOST pins, so a service pinned off the default port silently moves back to port scanning after an update.

Mechanics:

  • renderBootServicePlist (apps/server/src/cloud/bootService.ts ~L134) emits exactly PATH, T3CODE_HOME, and T3_BOOT_SERVICE_UNIT. renderBootServiceUnit does the same for systemd.
  • install() unconditionally runs writeDurably(unitPath, manager.render(plan)) (~L885). The only value read back from an existing unit is T3CODE_HOME via bootServiceBaseDirOf (~L64).
  • t3 update calls install() on every version switch (apps/server/src/cli/update.ts ~L559, comment: "The unit is rewritten either way").
  • On Linux a systemctl --user edit drop-in would survive this. launchd has no override mechanism, so on macOS there is nowhere to put service environment that t3 will not delete.
  • docs/user/source-control.md tells Bitbucket users to export the variables "in the server's environment" and never mentions the background service; docs/user/background-service.md never mentions environment. Bitbucket is the only provider that needs environment, because it has no CLI to hold credentials.

Suggested fixes (any one would do):

  1. Preserve unknown EnvironmentVariables / Environment= entries when re-rendering an existing unit.
  2. Have the launcher read an optional env file (e.g. $T3CODE_HOME/service.env) and document it.
  3. Store Bitbucket credentials in the server's existing secrets store, settable from Settings → Source Control, so no environment is needed.

Impact

Blocks a workflow (Bitbucket source control cannot be kept working on a macOS background service across updates)

Version or commit

0.0.42 → 0.0.43-nightly.20260919.1962

Environment

macOS 26.6 (Darwin 25.6.0), arm64, background service via launchd

Logs or stack traces

# after `t3 update`, the regenerated plist EnvironmentVariables dict contains only:
PATH, T3CODE_HOME, T3_BOOT_SERVICE_UNIT

Workaround

Keep the variables out of t3's plist: a separate user LaunchAgent that runs launchctl setenv T3CODE_BITBUCKET_EMAIL … ; launchctl setenv T3CODE_BITBUCKET_API_TOKEN … ; launchctl kickstart -k gui/$UID/com.t3tools.t3code.service at login. The service inherits the domain environment and t3 never touches that file.

Activity

  1. juliusmarminge commented on Sep 19, 2026

    @juliusmarminge
    Member

    Thanks for the precise write-up — this checks out on current main.

    t3 service install and t3 update always re-render the unit from a closed template (renderBootServicePlist / renderBootServiceUnit in apps/server/src/cloud/bootService.ts) and write it with writeDurably. The only value read back from an existing unit is T3CODE_HOME. Extra EnvironmentVariables / Environment= entries are discarded. t3 update does this on every version switch (apps/server/src/cli/update.ts).

    Bitbucket is the sharp edge because it is env-only (T3CODE_BITBUCKET_EMAIL / T3CODE_BITBUCKET_API_TOKEN / T3CODE_BITBUCKET_ACCESS_TOKEN in BitbucketApi.ts) and Settings → Source Control has no place to store those credentials. The same wipe also drops T3CODE_PORT / T3CODE_HOST. Linux can keep extras in a systemctl --user edit drop-in; launchd has no equivalent, so on macOS there is currently no supported place for service environment that survives an update.

    This is not a duplicate of #5840 / #9022 (desktop Dock launch vs shell profile). Same credentials, different surface.

    Workaround (as you described): keep the variables out of t3’s plist — a separate user LaunchAgent that launchctl setenvs them and kickstarts gui/$UID/com.t3tools.t3code.service.

    Likely fix: an optional env file under T3 home that the launcher merges, documented from both the background-service and source-control guides. Preserving unknown plist/unit keys is more fragile. Putting Bitbucket in the secrets store + Settings is the better long-term product, but it does not cover port/host pins and is a larger change.

    Accepting as a bug. No further info needed.

  2. added
    acceptedfeature request accepted
    bugSomething is broken or behaving incorrectly.
    via-triageFiled through npx t3 triage
    on Sep 19, 2026
  3. cestercian commented on Sep 19, 2026

    @cestercian
    Contributor

    Taking this — will preserve user environment variables across t3 update / t3 service install unit re-renders.

  4. gamelaster commented on Oct 7, 2026

    @gamelaster

    I would add to this, that in case I want to install T3 service, it would be nice to have --host parameter, so I can configure, if it is only for local mode, or for 0.0.0.0/tailscale.

  5. added a commit that references this issue on Oct 8, 2026
    e5ab7f3
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    acceptedfeature request acceptedbugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions