Skip to content

Working directory auto cd#9

Open
koudis wants to merge 5 commits into
mainfrom
working_directory_auto_cd
Open

Working directory auto cd#9
koudis wants to merge 5 commits into
mainfrom
working_directory_auto_cd

Conversation

@koudis

@koudis koudis commented Jul 3, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features
    • SSH sessions now open directly in /workspace by default (login-time working directory setup).
    • Updated documentation to clarify agent credentials are bind-mounted, and expanded primary IDE support to include Codium.
  • Bug Fixes
    • If /workspace isn’t available, SSH login continues gracefully (workspace switch is skipped without breaking the session).
  • Tests
    • Added unit and end-to-end integration coverage validating the SSH login working directory and related SSH/workspace behaviors.

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds a workspace cd-on-login feature: a new WorkspaceProfileScript constant, base image builder wiring for /etc/profile.d/workspace-cd.sh, documentation updates for the SSH-login working directory behavior, and expanded Docker integration coverage for container, network, and SSH session lifecycle behavior.

Changes

Workspace cd-on-login feature

Layer / File(s) Summary
Workspace profile contract
internal/constants/constants.go, .kiro/specs/bootstrap-ai-coding/requirements-core.md, .kiro/specs/bootstrap-ai-coding/design-properties.md, .kiro/specs/bootstrap-ai-coding/design-components.md, .kiro/specs/bootstrap-ai-coding/design-build-resources.md, .kiro/steering/constants.md, .kiro/steering/structure.md, README.md
Adds WorkspaceProfileScript and documents the SSH-login /workspace default-directory behavior across requirements, design, steering, and README updates.
Base image script wiring
internal/docker/builder.go, internal/docker/builder_test.go
Writes the workspace login script into the base image Dockerfile, marks it executable, and checks the generated Dockerfile for both create and rename user strategies.
Shared integration test harness
internal/docker/integration_main_test.go
Adds the integration test main setup, shared-image build path, per-test container startup helper, and supporting port/name/image utilities.
Container and image lifecycle checks
internal/docker/integration_container_test.go, internal/docker/integration_lifecycle_test.go, internal/docker/integration_misc_test.go
Adds integration tests for SSH reachability, workspace mount sync, file ownership, hostname matching, restart/purge behavior, build timeout handling, conflicting-user image pulling, and read-only mounts.
Network and SSH session behavior
internal/docker/integration_network_test.go, internal/docker/integration_ssh_test.go
Adds host-network and bridge-network SSH checks, host-service reachability, SSH state persistence, known_hosts and SSH config lifecycle checks, and login-shell workspace/fallback assertions over SSH.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant BaseImageBuilder
  participant Dockerfile
  participant SSHClient
  participant Container

  BaseImageBuilder->>Dockerfile: write /etc/profile.d/workspace-cd.sh
  BaseImageBuilder->>Dockerfile: chmod +x workspace-cd.sh
  SSHClient->>Container: connect over SSH
  SSHClient->>Container: run "bash -l -c pwd"
  Container-->>SSHClient: working directory output
  Container-->>SSHClient: fallback to home dir if /workspace is missing
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main change: SSH login now auto-cds into the workspace directory.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch working_directory_auto_cd

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (7)
internal/docker/integration_lifecycle_test.go (1)

62-91: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Purge test can leak the container if an earlier assertion fails.

TestPurgeRemovesContainersAndImages intentionally skips registering cleanup (line 70) so the manual stop/remove is itself part of the test. If require.NotNil at line 78 (or any earlier require) fails, t.FailNow() aborts before the container is stopped/removed, leaking it for subsequent runs.

Consider registering a t.Cleanup safety net (force stop+remove, ignoring errors) in addition to the explicit purge-logic assertions — it won't interfere with the "container is gone" check since removing an already-removed container is a no-op.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/docker/integration_lifecycle_test.go` around lines 62 - 91, The
purge test in TestPurgeRemovesContainersAndImages can leak a container if any
earlier require fails before the manual stop/remove path runs. Add a t.Cleanup
safety net right after startContainerFromSharedImage that forcefully stops and
removes the container using docker.StopContainer and docker.RemoveContainer,
ignoring cleanup errors, while keeping the explicit purge assertions in the test
body so the container lifecycle is still validated.
internal/docker/integration_main_test.go (3)

85-92: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated "resolve user strategy + build base/instance image" boilerplate across files.

This same ~15-line pattern (FindConflictingUser → strategy/conflictingUser → NewBaseImageBuilder/NewInstanceImageBuilder) is repeated near-verbatim in integration_lifecycle_test.go (TestTwoLayerBuildCycle), all three tests in integration_network_test.go, and integration_ssh_test.go (buildAndGetFingerprint). Consider extracting a shared helper here (e.g. resolveUserStrategy(ctx, client, info) and/or buildBaseAndInstanceImages(...)) that all the other integration test files can reuse.

Also applies to: 100-105

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/docker/integration_main_test.go` around lines 85 - 92, The
duplicated user-strategy and image-builder setup logic should be extracted into
shared helpers instead of being repeated across integration tests. Create a
reusable helper around the FindConflictingUser call and strategy/conflictingUser
selection (for example resolveUserStrategy(ctx, client, info)), and consider a
second helper for the NewBaseImageBuilder/NewInstanceImageBuilder sequence so
TestTwoLayerBuildCycle, the integration_network tests, buildAndGetFingerprint in
integration_ssh_test, and this test can all call the same utility.

59-64: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Lazy shared-image init isn't safe against future parallelism.

sharedImageTag != "" guards a package-level singleton without a mutex/sync.Once. It's safe today only because no test calls t.Parallel(). If that changes later, this becomes a data race (and could trigger duplicate/racy image builds).

♻️ Suggested fix using sync.Once
+var buildOnce sync.Once
+
 func buildSharedImage(t *testing.T) {
 	t.Helper()
-
-	if sharedImageTag != "" {
-		return // already built
-	}
-
-	ctx := context.Background()
-	...
+	buildOnce.Do(func() { buildSharedImageOnce(t) })
+}
+
+func buildSharedImageOnce(t *testing.T) {
+	ctx := context.Background()
+	...
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/docker/integration_main_test.go` around lines 59 - 64, The
package-level shared-image initialization in buildSharedImage is guarded only by
sharedImageTag, which is unsafe if tests ever run in parallel. Update the
singleton initialization to use a sync.Once-based guard (or equivalent mutex
protection) around buildSharedImage so the shared image is built exactly once
without races, and keep the existing sharedImageTag check only as the cached
result inside that protected path.

190-199: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

findFreePort has a check-then-use race (TOCTOU).

The listener is closed immediately after probing, and the port isn't reserved until the container is actually started later. Another process (or a concurrently-running test binary) could grab the port in between, causing intermittent "address already in use" flakes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/docker/integration_main_test.go` around lines 190 - 199, The
findFreePort helper in integration_main_test.go has a TOCTOU race because it
probes a port with net.Listen and closes it before the container uses it. Fix
this by changing the flow around findFreePort and the container startup so the
chosen port stays reserved until the service is ready, or eliminate the separate
probe-and-reuse pattern by letting the container/runtime bind an available port
directly. Refer to findFreePort and the later container start path that consumes
its result when applying the change.
internal/docker/integration_network_test.go (1)

27-351: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract shared setup to remove ~90 lines of triplicated boilerplate.

TestHostNetworkModeSSHReachable, TestHostNetworkCanReachHostService, and TestBridgeModeSSHReachable each repeat the identical "generate keys → resolve user strategy → build base image → build instance image → create+start container" sequence, differing only in hostNetworkOff and a name prefix. Extracting a helper (e.g. buildAndStartNetworkContainer(t, namePrefix string, hostNetworkOff bool) (containerName string, sshPort int, client *docker.Client)) in integration_main_test.go would cut duplication significantly and reduce the chance of the three copies drifting out of sync.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/docker/integration_network_test.go` around lines 27 - 351, The three
network integration tests duplicate the same setup flow, so extract the shared
“generate keys → resolve user strategy → build base image → build instance image
→ create/start container” logic into a helper such as
buildAndStartNetworkContainer used by TestHostNetworkModeSSHReachable,
TestHostNetworkCanReachHostService, and TestBridgeModeSSHReachable. Keep the
per-test differences (name prefix, hostNetworkOff, and the host-service
connectivity assertion) in the test bodies, and reuse the helper’s outputs like
containerName, sshPort, and client to avoid the repeated boilerplate drifting
apart.
internal/docker/integration_ssh_test.go (1)

424-436: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Overly broad stderr assertion risks unrelated flakes.

require.Empty(t, stderr.String(), ...) asserts that no profile.d script emits anything to stderr during the login shell, not just the workspace-cd script under test. Since /etc/profile.d/dbus-keyring.sh (D-Bus/gnome-keyring startup) also runs during bash -l, any warning it emits (e.g., DBus launch issues in the container environment) would fail this test for reasons unrelated to the workspace-cd fallback behavior being validated (Req 27.3).

Consider scoping this check to just the workspace script, e.g. exec sh /etc/profile.d/workspace-cd.sh directly and check its own stderr, rather than asserting on the full login-shell stderr.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/docker/integration_ssh_test.go` around lines 424 - 436, The stderr
assertion in the login-shell flow is too broad because it captures output from
all profile.d scripts, not just the workspace-cd behavior being tested. Update
the test around session.Run("bash -l -c pwd") to avoid requiring stderr.String()
to be empty for the whole shell, and instead scope validation to the workspace
script itself by invoking workspace-cd.sh directly and checking only its stderr.
Keep the existing fallback checks in integration_ssh_test.go focused on pwd
output and info.HomeDir.
internal/docker/integration_misc_test.go (1)

28-67: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Docker build-cache may make this test flaky/false-negative.

spec doesn't set NoCache: true. If a prior run already built an image with the same FROM <base> + RUN sleep 300 instruction (even under a different tag, since Docker's build cache is keyed on instruction+parent-layer, not the target tag), a subsequent build can hit the cached layer and finish well under testBuildTimeout, causing require.Error to fail for reasons unrelated to the timeout logic under test.

♻️ Suggested fix
 	spec := docker.ContainerSpec{
 		Name:       containerName,
 		ImageTag:   imageTag,
 		Dockerfile: hangingDockerfile,
 		Labels:     map[string]string{"bac.managed": "true"},
+		NoCache:    true,
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/docker/integration_misc_test.go` around lines 28 - 67, The timeout
test in TestBuildImageTimeoutEnforced can be a false negative because Docker may
reuse a cached layer for the hanging Dockerfile. Update the ContainerSpec used
for docker.BuildImageWithTimeout to disable build cache (for example via the
NoCache field) so the RUN sleep 300 step is always executed, keeping the test
focused on timeout enforcement.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/docker/integration_misc_test.go`:
- Around line 70-101: TestAFindConflictingUserPullsImageIfAbsent currently
depends on test ordering and TestMain to ensure the base image is absent, so it
may miss the intended code path if other tests pull it first. Update this test
to call testutil.EnsureBaseImageAbsent() at the start before creating the Docker
client and invoking docker.FindConflictingUser, so the “image absent” scenario
is enforced regardless of package order.

In `@internal/docker/integration_ssh_test.go`:
- Around line 71-131: The SSH fingerprint check in buildAndGetFingerprint is
tautological because it returns the captured hostKeyPub input instead of
verifying the rebuilt artifact. Update buildAndGetFingerprint and the final
require.Equal assertion to extract the actual SSH host key from each rebuilt
image/container, using the existing docker.BuildImage flow plus a real readback
path such as SSH HostKeyCallback or docker.ExecInContainer against the instance
built by NewInstanceImageBuilder, then compare those observed fingerprints
rather than the originally generated variable.

---

Nitpick comments:
In `@internal/docker/integration_lifecycle_test.go`:
- Around line 62-91: The purge test in TestPurgeRemovesContainersAndImages can
leak a container if any earlier require fails before the manual stop/remove path
runs. Add a t.Cleanup safety net right after startContainerFromSharedImage that
forcefully stops and removes the container using docker.StopContainer and
docker.RemoveContainer, ignoring cleanup errors, while keeping the explicit
purge assertions in the test body so the container lifecycle is still validated.

In `@internal/docker/integration_main_test.go`:
- Around line 85-92: The duplicated user-strategy and image-builder setup logic
should be extracted into shared helpers instead of being repeated across
integration tests. Create a reusable helper around the FindConflictingUser call
and strategy/conflictingUser selection (for example resolveUserStrategy(ctx,
client, info)), and consider a second helper for the
NewBaseImageBuilder/NewInstanceImageBuilder sequence so TestTwoLayerBuildCycle,
the integration_network tests, buildAndGetFingerprint in integration_ssh_test,
and this test can all call the same utility.
- Around line 59-64: The package-level shared-image initialization in
buildSharedImage is guarded only by sharedImageTag, which is unsafe if tests
ever run in parallel. Update the singleton initialization to use a
sync.Once-based guard (or equivalent mutex protection) around buildSharedImage
so the shared image is built exactly once without races, and keep the existing
sharedImageTag check only as the cached result inside that protected path.
- Around line 190-199: The findFreePort helper in integration_main_test.go has a
TOCTOU race because it probes a port with net.Listen and closes it before the
container uses it. Fix this by changing the flow around findFreePort and the
container startup so the chosen port stays reserved until the service is ready,
or eliminate the separate probe-and-reuse pattern by letting the
container/runtime bind an available port directly. Refer to findFreePort and the
later container start path that consumes its result when applying the change.

In `@internal/docker/integration_misc_test.go`:
- Around line 28-67: The timeout test in TestBuildImageTimeoutEnforced can be a
false negative because Docker may reuse a cached layer for the hanging
Dockerfile. Update the ContainerSpec used for docker.BuildImageWithTimeout to
disable build cache (for example via the NoCache field) so the RUN sleep 300
step is always executed, keeping the test focused on timeout enforcement.

In `@internal/docker/integration_network_test.go`:
- Around line 27-351: The three network integration tests duplicate the same
setup flow, so extract the shared “generate keys → resolve user strategy → build
base image → build instance image → create/start container” logic into a helper
such as buildAndStartNetworkContainer used by TestHostNetworkModeSSHReachable,
TestHostNetworkCanReachHostService, and TestBridgeModeSSHReachable. Keep the
per-test differences (name prefix, hostNetworkOff, and the host-service
connectivity assertion) in the test bodies, and reuse the helper’s outputs like
containerName, sshPort, and client to avoid the repeated boilerplate drifting
apart.

In `@internal/docker/integration_ssh_test.go`:
- Around line 424-436: The stderr assertion in the login-shell flow is too broad
because it captures output from all profile.d scripts, not just the workspace-cd
behavior being tested. Update the test around session.Run("bash -l -c pwd") to
avoid requiring stderr.String() to be empty for the whole shell, and instead
scope validation to the workspace script itself by invoking workspace-cd.sh
directly and checking only its stderr. Keep the existing fallback checks in
integration_ssh_test.go focused on pwd output and info.HomeDir.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 779e5163-e43d-4667-a56e-39e70cad6e4e

📥 Commits

Reviewing files that changed from the base of the PR and between 495b889 and 4607e67.

📒 Files selected for processing (7)
  • internal/docker/integration_container_test.go
  • internal/docker/integration_lifecycle_test.go
  • internal/docker/integration_main_test.go
  • internal/docker/integration_misc_test.go
  • internal/docker/integration_network_test.go
  • internal/docker/integration_ssh_test.go
  • internal/docker/integration_test.go
💤 Files with no reviewable changes (1)
  • internal/docker/integration_test.go

Comment on lines +70 to +101
// TestAFindConflictingUserPullsImageIfAbsent
// Validates: Req 10a.1 — FindConflictingUser must succeed even when the base
// image is not present in the local Docker image store.
//
// Named with "A" prefix so Go's alphabetical test ordering runs this first.
// The base image is guaranteed absent by TestMain's call to
// EnsureBaseImageAbsent(), so this test simply calls FindConflictingUser and
// asserts it succeeds (pulling the image automatically).
// ----------------------------------------------------------------------------

func TestAFindConflictingUserPullsImageIfAbsent(t *testing.T) {
if _, err := exec.LookPath("docker"); err != nil {
t.Skip("docker not available")
}

ctx := context.Background()

client, err := docker.NewClient()
require.NoError(t, err, "connecting to Docker daemon")

info, err := hostinfo.Current()
require.NoError(t, err)

result, err := docker.FindConflictingUser(ctx, client, info.UID, info.GID)
require.NoError(t, err,
"FindConflictingUser must succeed even when the base image is not cached locally")
_ = result

_, _, err = client.ImageInspectWithRaw(ctx, constants.BaseContainerImage)
require.NoError(t, err,
"base image should be present locally after FindConflictingUser pulls it")
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm current cross-file test ordering assumption for the integration package
fd -e go -g '*_test.go' internal/docker | sort

Repository: koudis/bootstrap-ai-coding

Length of output: 594


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the integration test setup and any explicit ordering dependencies.
ast-grep outline internal/docker/integration_misc_test.go --view expanded || true
printf '\n--- TestMain / setup references ---\n'
rg -n "EnsureBaseImageAbsent|TestMain|startContainerFromSharedImage|buildSharedImage|FindConflictingUser|RunTests|Test[A-Z]" internal/docker/*.go

Repository: koudis/bootstrap-ai-coding

Length of output: 19588


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- internal/docker/integration_main_test.go ---'
sed -n '1,220p' internal/docker/integration_main_test.go

printf '\n%s\n' '--- internal/docker/integration_container_test.go ---'
sed -n '1,170p' internal/docker/integration_container_test.go

printf '\n%s\n' '--- internal/docker/integration_lifecycle_test.go ---'
sed -n '1,210p' internal/docker/integration_lifecycle_test.go

Repository: koudis/bootstrap-ai-coding

Length of output: 18862


🌐 Web query:

Go testing package order of execution top-level tests file order alphabetical filenames order guarantee

💡 Result:

In Go, there is no guarantee regarding the execution order of top-level tests [1]. The Go testing framework does not specify a required order, and it is considered a best practice for unit tests to be independent of each other [2]. While some observers may note that tests often execute in a consistent order (such as alphabetically by filename) under specific local conditions, this behavior is not guaranteed by the language specification or the testing tool [1]. Relying on any specific execution order is discouraged. To manage execution order, you should avoid dependencies between tests. If you require specific control over test execution, you can use the following mechanisms: 1. Shuffle Flag: You can use the -shuffle flag (e.g., -shuffle on) with the go test command to randomize the execution order [3][2]. This is useful for identifying hidden dependencies between tests, as it forces them to run in a non-deterministic sequence. 2. Subtests: For related tests that require a specific sequence, you can group them within a single test function using subtests (t.Run) [4][5][6]. Subtests execute in the order they are defined within the parent test function. 3. Test Suites or Frameworks: If complex setup and teardown order are required, consider using standard testing patterns or established third-party testing frameworks that provide explicit lifecycle management. In summary, the execution order of top-level tests is undefined, and you should design your test suite to be resilient to any order of execution [1][2].

Citations:


Make this test independent of package order
Go test does not guarantee top-level test order, so the A prefix/file name won’t reliably make this run before other integration tests. startContainerFromSharedImage can pull the base image first, which means this no longer covers the “image absent” path. Call testutil.EnsureBaseImageAbsent() at the start of this test instead of relying on TestMain alone.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/docker/integration_misc_test.go` around lines 70 - 101,
TestAFindConflictingUserPullsImageIfAbsent currently depends on test ordering
and TestMain to ensure the base image is absent, so it may miss the intended
code path if other tests pull it first. Update this test to call
testutil.EnsureBaseImageAbsent() at the start before creating the Docker client
and invoking docker.FindConflictingUser, so the “image absent” scenario is
enforced regardless of package order.

Comment on lines +71 to +131
buildAndGetFingerprint := func() string {
t.Helper()

strategy := docker.UserStrategyCreate
conflictingUser := ""
conflictingImageUser, err := docker.FindConflictingUser(ctx, client, info.UID, info.GID)
require.NoError(t, err, "checking base image for UID/GID conflicts")
if conflictingImageUser != nil {
strategy = docker.UserStrategyRename
conflictingUser = conflictingImageUser.Username
}

builder := docker.NewBaseImageBuilder(
info,
strategy, conflictingUser,
"",
)

// Build base image
baseSpec := docker.ContainerSpec{
Name: containerName,
ImageTag: constants.BaseImageTag,
Dockerfile: builder.Build(),
Labels: map[string]string{"bac.managed": "true"},
HostInfo: info,
}

_, err = docker.BuildImage(ctx, client, baseSpec, false)
require.NoError(t, err, "building base image")

// Build instance image
instanceBuilder := docker.NewInstanceImageBuilder(
info,
userPubKey,
hostKeyPriv, hostKeyPub,
2222, false,
)
instanceBuilder.Finalize()
spec := docker.ContainerSpec{
Name: containerName,
ImageTag: imageTag,
Dockerfile: instanceBuilder.Build(),
Mounts: []docker.Mount{
{HostPath: projectDir, ContainerPath: constants.WorkspaceMountPath},
},
SSHPort: port,
Labels: map[string]string{"bac.managed": "true"},
HostInfo: info,
}

_, err = docker.BuildImage(ctx, client, spec, false)
require.NoError(t, err, "building instance image")

return hostKeyPub
}

fingerprint1 := buildAndGetFingerprint()
fingerprint2 := buildAndGetFingerprint()

require.Equal(t, fingerprint1, fingerprint2,
"SSH host key fingerprint must be stable across rebuilds")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Test is tautological — it can never fail regardless of actual rebuild behavior.

buildAndGetFingerprint closes over the single hostKeyPub value generated once at line 40, and returns that exact same variable both times it's called (line 124). fingerprint1 and fingerprint2 are therefore always the identical string by construction — the require.Equal at line 130 provides no real signal about whether the built image/container actually persists or re-derives the SSH host key correctly across rebuilds; it only checks that a variable equals itself.

To genuinely validate Req 13.3, the test needs to read back the host key that's actually baked into (or presented by) each rebuilt artifact — e.g. dial SSH against a container started from each build and capture the presented host key via a custom HostKeyCallback, or docker.ExecInContainer + cat/hash the host key file inside the container — and compare those extracted values instead of the input parameter.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/docker/integration_ssh_test.go` around lines 71 - 131, The SSH
fingerprint check in buildAndGetFingerprint is tautological because it returns
the captured hostKeyPub input instead of verifying the rebuilt artifact. Update
buildAndGetFingerprint and the final require.Equal assertion to extract the
actual SSH host key from each rebuilt image/container, using the existing
docker.BuildImage flow plus a real readback path such as SSH HostKeyCallback or
docker.ExecInContainer against the instance built by NewInstanceImageBuilder,
then compare those observed fingerprints rather than the originally generated
variable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant