Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .changes/unreleased/BUG FIXES-20260729-100121.yaml

This file was deleted.

3 changes: 0 additions & 3 deletions .changes/unreleased/BUG FIXES-20260729-100210.yaml

This file was deleted.

3 changes: 0 additions & 3 deletions .changes/unreleased/BUG FIXES-20260729-101327.yaml

This file was deleted.

3 changes: 0 additions & 3 deletions .changes/unreleased/ENHANCEMENTS-20260724-172011.yaml

This file was deleted.

3 changes: 0 additions & 3 deletions .changes/unreleased/ENHANCEMENTS-20260728-150652.yaml

This file was deleted.

43 changes: 43 additions & 0 deletions .changes/v0.4.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
## v0.4.0 (July 31, 2026)

NEW FEATURES:

* This release adds `harness exec`. The command lets a user give noninteractive `tfctl` delete permission to a wrapped command. Use `--allow-delete=resources,other-resources` to give permission to delete those resource types for one session. The permission applies to all subprocesses.

* tfctl automatically updates outdated tfctl skills that it installed. tfctl does not update modified skills.

ENHANCEMENTS:

* tfctl now checks for a newer release when you run `tfctl`, `tfctl version`, or `tfctl --version`. These commands also tell you when you must run `auth login`.

* `run start` now has a `--plan-only` flag. The flag creates a speculative plan-only run. You cannot apply this run, regardless of the workspace auto-apply setting.

* tfctl now checks positional arguments and API path parameters for control characters and invalid UTF-8. It rejects malformed values before requests, terminal output, and audit output. These checks are not a security boundary. Your API token still controls authorization.

* The `TFCTL_CONFIG_DIR` environment variable can now set the configuration directory. Profiles and exec sessions use the specified directory. Use this variable to isolate tfctl state in continuous integration or test harnesses.

BUG FIXES:

* `auth login --dry-run` no longer opens a web browser. Argument completion now includes `--dry-run`.

* `profile display --markdown` no longer returns an error.

* The `profile profiles list --json` output no longer includes the Token property. This change prevents accidental token exposure.

* tfctl no longer tries to send telemetry again after responses such as the 429 rate-limit response.

* Authentication now detects Terraform `TF_TOKEN_<hostname>` environment variables, such as `TF_TOKEN_app_terraform_io`. This behavior matches Terraform CLI token resolution. Detection supports punycode hostnames and both Terraform dash encodings. You can encode each dash as `-` or `__`. tfctl did not previously detect these tokens.

* The not-found (404) API error now tells you to verify the request path and resource IDs. It no longer suggests an authentication problem.

* `api --all` now returns output when a result has only one page. Previously, the pagination check read the response body but did not restore it. Thus, the command returned no output.

* `api --json` and `api --jq` no longer put one-to-one relationship IDs in `attributes`. Table and pretty output still show these IDs. Raw JSON output remains unchanged.

* Telemetry payloads now redact customer data in HTTP paths, such as organization and workspace names.

* `--quiet` no longer suppresses output from `api` GET requests.

NOTES:

* This release adds `CONTRIBUTING.md`, `AGENTS.md`, developer setup automation, and `make help`. These resources make the initial development setup easier.
56 changes: 18 additions & 38 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,63 +1,43 @@
## v0.4.0-beta (July 23, 2026)

## v0.4.0 (July 31, 2026)

NEW FEATURES:

* Adds the `harness exec` command, which lets a human grant session-scoped, noninteractive `tfctl` delete permissions to a wrapped command (such as a coding agent) via `--allow-delete`. The grant is tied to all subprocesses.
* This release adds `harness exec`. The command lets a user give noninteractive `tfctl` delete permission to a wrapped command. Use `--allow-delete=resources,other-resources` to give permission to delete those resource types for one session. The permission applies to all subprocesses.

* tfctl automatically updates outdated tfctl skills that it installed. tfctl does not update modified skills.

ENHANCEMENTS:

* Adds a `--plan-only` flag to `run start` that creates a speculative plan-only run which is never applied, regardless of the workspace's auto-apply setting.
* tfctl now checks for a newer release when you run `tfctl`, `tfctl version`, or `tfctl --version`. These commands also tell you when you must run `auth login`.

* Positional arguments and API path parameters are now checked for basic input hygiene, rejecting control characters and invalid UTF-8. This keeps malformed values out of requests and out of terminal/audit output. This is not a security boundary; authorization is still enforced by your API token.
* `run start` now has a `--plan-only` flag. The flag creates a speculative plan-only run. You cannot apply this run, regardless of the workspace auto-apply setting.

* The config directory can now be overridden with the TFCTL_CONFIG_DIR environment variable. Profiles and exec sessions both resolve against this directory, making it easy to isolate tfctl state (for example, in CI or eval harnesses).
* tfctl now checks positional arguments and API path parameters for control characters and invalid UTF-8. It rejects malformed values before requests, terminal output, and audit output. These checks are not a security boundary. Your API token still controls authorization.

* The `TFCTL_CONFIG_DIR` environment variable can now set the configuration directory. Profiles and exec sessions use the specified directory. Use this variable to isolate tfctl state in continuous integration or test harnesses.

BUG FIXES:

* Don't attempt to retry telemetry transmission on server errors, like rate limiting 429 errors.

* Detect Terraform's `TF_TOKEN_<hostname>` environment variables (such as `TF_TOKEN_app_terraform_io`) during authentication, matching Terraform CLI's resolution. This includes punycode hostnames and the interchangeable dash encodings (literal `-` or double underscore). Previously these tokens were not detected.

* Clarify the not-found (404) API error message to point at verifying the request path and resource IDs instead of suggesting an authentication problem.

* Fix `api --all` returning no output when the result fits in a single page. The response body was consumed while checking for additional pages and not restored, so single-page responses rendered empty.

* Fix `api` `--json` and `--jq` output incorrectly including one-to-one relationship IDs under `attributes`. Those IDs are surfaced for table and pretty output only, and are no longer written back into the raw JSON payload.

* Redacts customer data within HTTP paths in telemetry payloads, such as organization and workspace names.

* Using the --quiet argument no longer suppresses api command rendering for GET requests.


NOTES:

* For developers of tfctl, added CONTRIBUTING.md, developer setup automation, and `make help`

## v0.3.0 (June 22, 2026)
* `auth login --dry-run` no longer opens a web browser. Argument completion now includes `--dry-run`.

* `profile display --markdown` no longer returns an error.

ENHANCEMENTS:

* The `harness install` command supports shell autocompletion for supported coding agents and support for the Amp coding agent has been added.
* The `profile profiles list --json` output no longer includes the Token property. This change prevents accidental token exposure.

* Adds debug logging for token configuration sources.
* tfctl no longer tries to send telemetry again after responses such as the 429 rate-limit response.

* Hostnames are normalized before storage within profiles.
* Authentication now detects Terraform `TF_TOKEN_<hostname>` environment variables, such as `TF_TOKEN_app_terraform_io`. This behavior matches Terraform CLI token resolution. Detection supports punycode hostnames and both Terraform dash encodings. You can encode each dash as `-` or `__`. tfctl did not previously detect these tokens.

* The `api` command now accepts arbitrary URLs, such as Archivist, but does not send tokens to any host except the configured API host.
* The not-found (404) API error now tells you to verify the request path and resource IDs. It no longer suggests an authentication problem.

* `api --all` now returns output when a result has only one page. Previously, the pagination check read the response body but did not restore it. Thus, the command returned no output.

BUG FIXES:

* Profile configuration files are now created with read/write permissions for owner only.
* `api --json` and `api --jq` no longer put one-to-one relationship IDs in `attributes`. Table and pretty output still show these IDs. Raw JSON output remains unchanged.

* Hostname telemetry is anonymized when configured with a Terraform Enterprise host.
* Telemetry payloads now redact customer data in HTTP paths, such as organization and workspace names.

## v0.2.0 (June 12, 2026)
* `--quiet` no longer suppresses output from `api` GET requests.

NOTES:

* tfctl is an agent-first, human-friendly CLI for accessing HCP Terraform and Terraform Enterprise. Future changes will be documented here. For now, see README.md for installation, usage, and a command reference.
* This release adds `CONTRIBUTING.md`, `AGENTS.md`, developer setup automation, and `make help`. These resources make the initial development setup easier.
10 changes: 6 additions & 4 deletions internal/commands/harness/harness_exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,14 @@ func NewCmdHarnessExec(inv *cmd.Invocation) *cmd.Command {

This is a deliberate, per-session opt-in by a human. The permission is tied to the lifetime of this process and {{ Bold "auto-reverts" }} to the safe default (deletes require an interactive confirmation) as soon as the child exits.

This is a {{ Bold "safety rail, not a security boundary" }}: the child runs as the same OS user, so a true guarantee that an agent cannot delete must come from the API token scope server-side.
This is a {{ Bold "safety rail, not a security boundary" }}: the child runs as the same OS user, so a true guarantee that an agent cannot delete must come from the API token permissions set by the server.

Use {{ template "mdCodeOrBold" "--allow-delete" }} to name the resource types that may be deleted noninteractively. Only resource types that are explicitly named may be deleted noninteractively. Repeat the flag or pass a comma-separated list.

Some possible values for {{ template "mdCodeOrBold" "--allow-delete" }} are: %s }}.

The child command and its arguments must follow a {{ template "mdCodeOrBold" "--" }} separator.
`, version.Name),
`, version.Name, strings.Join(execsession.DestroyableResourceTypes(), ", ")),
Examples: []cmd.Example{
{
Preamble: "Allow an agent to delete workspaces and vars for one session:",
Expand Down Expand Up @@ -100,10 +102,10 @@ func NewCmdHarnessExec(inv *cmd.Invocation) *cmd.Command {
{
Name: "allow-delete",
DisplayValue: "RESOURCE_TYPES",
Description: "Resource types that nested processes may delete noninteractively (repeatable, CSV). Use with caution.",
Description: "Resource types that nested processes may delete noninteractively. May use comma-separated list. Use with caution.",
Repeatable: true,
Value: flagvalue.SimpleSlice(nil, &execOpts.AllowDelete),
Autocomplete: complete.PredictSet(execsession.AllowDeleteCompletions()...),
Autocomplete: complete.PredictSet(execsession.DestroyableResourceTypes()...),
},
},
},
Expand Down
6 changes: 4 additions & 2 deletions internal/pkg/execsession/permissions.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ package execsession

import (
"fmt"
"slices"
"strings"

"github.com/hashicorp/tfctl-cli/internal/pkg/resource"
)

// AllowDeleteCompletions returns the suggested values for --allow-delete: every
// DestroyableResourceTypes returns the suggested values for --allow-delete: every
// known destroyable resource class.
func AllowDeleteCompletions() []string {
func DestroyableResourceTypes() []string {
allResources := resource.All()

out := make([]string, 0, len(allResources))
Expand All @@ -21,6 +22,7 @@ func AllowDeleteCompletions() []string {
out = append(out, r.Type)
}
}
slices.Sort(out)
return out
}

Expand Down
4 changes: 2 additions & 2 deletions internal/pkg/execsession/permissions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ import (
"github.com/hashicorp/tfctl-cli/internal/pkg/resource"
)

func TestAllowDeleteCompletions(t *testing.T) {
func TestDestroyableResourceTypes(t *testing.T) {
t.Parallel()

got := AllowDeleteCompletions()
got := DestroyableResourceTypes()
all := resource.AllDestroyable()

// Every known class must be offered, including the irreversible ones so a
Expand Down
Loading